image.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { hashFnv32a } from '../../../mol-data/util';
  7. import { LocationIterator } from '../../../mol-geo/util/location-iterator';
  8. import { RenderableState } from '../../../mol-gl/renderable';
  9. import { calculateTransformBoundingSphere, createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
  10. import { Sphere3D } from '../../../mol-math/geometry';
  11. import { Vec2, Vec4, Vec3 } from '../../../mol-math/linear-algebra';
  12. import { Theme } from '../../../mol-theme/theme';
  13. import { ValueCell } from '../../../mol-util';
  14. import { Color } from '../../../mol-util/color';
  15. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  16. import { BaseGeometry } from '../base';
  17. import { createColors } from '../color-data';
  18. import { GeometryUtils } from '../geometry';
  19. import { createMarkers } from '../marker-data';
  20. import { createEmptyOverpaint } from '../overpaint-data';
  21. import { TransformData } from '../transform-data';
  22. import { createEmptyTransparency } from '../transparency-data';
  23. import { ImageValues } from '../../../mol-gl/renderable/image';
  24. import { fillSerial } from '../../../mol-util/array';
  25. import { createEmptyClipping } from '../clipping-data';
  26. import { NullLocation } from '../../../mol-model/location';
  27. import { QuadPositions } from '../../../mol-gl/compute/util';
  28. import { createEmptySubstance } from '../substance-data';
  29. const QuadIndices = new Uint32Array([
  30. 0, 1, 2,
  31. 1, 3, 2
  32. ]);
  33. const QuadUvs = new Float32Array([
  34. 0, 1,
  35. 0, 0,
  36. 1, 1,
  37. 1, 0
  38. ]);
  39. export const InterpolationTypes = {
  40. 'nearest': 'Nearest',
  41. 'catmulrom': 'Catmulrom (Cubic)',
  42. 'mitchell': 'Mitchell (Cubic)',
  43. 'bspline': 'B-Spline (Cubic)'
  44. };
  45. export type InterpolationTypes = keyof typeof InterpolationTypes;
  46. export const InterpolationTypeNames = Object.keys(InterpolationTypes) as InterpolationTypes[];
  47. export { Image };
  48. interface Image {
  49. readonly kind: 'image',
  50. readonly imageTexture: ValueCell<TextureImage<Uint8Array>>,
  51. readonly imageTextureDim: ValueCell<Vec2>,
  52. readonly cornerBuffer: ValueCell<Float32Array>,
  53. readonly groupTexture: ValueCell<TextureImage<Uint8Array>>,
  54. /** Bounding sphere of the image */
  55. boundingSphere: Sphere3D
  56. }
  57. namespace Image {
  58. export function create(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, image?: Image): Image {
  59. return image ?
  60. update(imageTexture, corners, groupTexture, image) :
  61. fromData(imageTexture, corners, groupTexture);
  62. }
  63. function hashCode(image: Image) {
  64. return hashFnv32a([
  65. image.cornerBuffer.ref.version
  66. ]);
  67. }
  68. function fromData(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>): Image {
  69. const boundingSphere = Sphere3D();
  70. let currentHash = -1;
  71. const width = imageTexture.width;
  72. const height = imageTexture.height;
  73. const image = {
  74. kind: 'image' as const,
  75. imageTexture: ValueCell.create(imageTexture),
  76. imageTextureDim: ValueCell.create(Vec2.create(width, height)),
  77. cornerBuffer: ValueCell.create(corners),
  78. groupTexture: ValueCell.create(groupTexture),
  79. get boundingSphere() {
  80. const newHash = hashCode(image);
  81. if (newHash !== currentHash) {
  82. const b = getBoundingSphere(image.cornerBuffer.ref.value);
  83. Sphere3D.copy(boundingSphere, b);
  84. currentHash = newHash;
  85. }
  86. return boundingSphere;
  87. },
  88. };
  89. return image;
  90. }
  91. function update(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, image: Image): Image {
  92. const width = imageTexture.width;
  93. const height = imageTexture.height;
  94. ValueCell.update(image.imageTexture, imageTexture);
  95. ValueCell.update(image.imageTextureDim, Vec2.set(image.imageTextureDim.ref.value, width, height));
  96. ValueCell.update(image.cornerBuffer, corners);
  97. ValueCell.update(image.groupTexture, groupTexture);
  98. return image;
  99. }
  100. export function createEmpty(image?: Image): Image {
  101. const imageTexture = createTextureImage(0, 4, Uint8Array);
  102. const corners = image ? image.cornerBuffer.ref.value : new Float32Array(8 * 3);
  103. const groupTexture = createTextureImage(0, 4, Uint8Array);
  104. return create(imageTexture, corners, groupTexture, image);
  105. }
  106. export const Params = {
  107. ...BaseGeometry.Params,
  108. interpolation: PD.Select('bspline', PD.objectToOptions(InterpolationTypes)),
  109. };
  110. export type Params = typeof Params
  111. export const Utils: GeometryUtils<Image, Params> = {
  112. Params,
  113. createEmpty,
  114. createValues,
  115. createValuesSimple,
  116. updateValues,
  117. updateBoundingSphere,
  118. createRenderableState,
  119. updateRenderableState,
  120. createPositionIterator: () => LocationIterator(1, 1, 1, () => NullLocation)
  121. };
  122. function createValues(image: Image, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): ImageValues {
  123. const { instanceCount, groupCount } = locationIt;
  124. const positionIt = Utils.createPositionIterator(image, transform);
  125. const color = createColors(locationIt, positionIt, theme.color);
  126. const marker = createMarkers(instanceCount * groupCount);
  127. const overpaint = createEmptyOverpaint();
  128. const transparency = createEmptyTransparency();
  129. const material = createEmptySubstance();
  130. const clipping = createEmptyClipping();
  131. const counts = { drawCount: QuadIndices.length, vertexCount: QuadPositions.length / 3, groupCount, instanceCount };
  132. const invariantBoundingSphere = Sphere3D.clone(image.boundingSphere);
  133. const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
  134. return {
  135. dGeometryType: ValueCell.create('image'),
  136. ...color,
  137. ...marker,
  138. ...overpaint,
  139. ...transparency,
  140. ...material,
  141. ...clipping,
  142. ...transform,
  143. ...BaseGeometry.createValues(props, counts),
  144. aPosition: image.cornerBuffer,
  145. aUv: ValueCell.create(QuadUvs),
  146. elements: ValueCell.create(QuadIndices),
  147. // aGroup is used as a vertex index here, group id is in tGroupTex
  148. aGroup: ValueCell.create(fillSerial(new Float32Array(4))),
  149. boundingSphere: ValueCell.create(boundingSphere),
  150. invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
  151. uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
  152. dInterpolation: ValueCell.create(props.interpolation),
  153. uImageTexDim: image.imageTextureDim,
  154. tImageTex: image.imageTexture,
  155. tGroupTex: image.groupTexture,
  156. };
  157. }
  158. function createValuesSimple(image: Image, props: Partial<PD.Values<Params>>, colorValue: Color, sizeValue: number, transform?: TransformData) {
  159. const s = BaseGeometry.createSimple(colorValue, sizeValue, transform);
  160. const p = { ...PD.getDefaultValues(Params), ...props };
  161. return createValues(image, s.transform, s.locationIterator, s.theme, p);
  162. }
  163. function updateValues(values: ImageValues, props: PD.Values<Params>) {
  164. BaseGeometry.updateValues(values, props);
  165. ValueCell.updateIfChanged(values.dInterpolation, props.interpolation);
  166. }
  167. function updateBoundingSphere(values: ImageValues, image: Image) {
  168. const invariantBoundingSphere = Sphere3D.clone(image.boundingSphere);
  169. const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
  170. if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
  171. ValueCell.update(values.boundingSphere, boundingSphere);
  172. }
  173. if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) {
  174. ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere);
  175. ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere));
  176. }
  177. }
  178. function createRenderableState(props: PD.Values<Params>): RenderableState {
  179. const state = BaseGeometry.createRenderableState(props);
  180. state.opaque = false;
  181. return state;
  182. }
  183. function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
  184. BaseGeometry.updateRenderableState(state, props);
  185. state.opaque = false;
  186. }
  187. }
  188. //
  189. function getBoundingSphere(corners: Float32Array) {
  190. const center = Vec3();
  191. const extrema: Vec3[] = [];
  192. for (let i = 0, il = corners.length; i < il; i += 3) {
  193. const e = Vec3.fromArray(Vec3(), corners, i);
  194. extrema.push(e);
  195. Vec3.add(center, center, e);
  196. }
  197. Vec3.scale(center, center, 1 / (corners.length / 3));
  198. let radius = 0;
  199. for (const e of extrema) {
  200. const d = Vec3.distance(center, e);
  201. if (d > radius) radius = d;
  202. }
  203. const sphere = Sphere3D.create(center, radius);
  204. Sphere3D.setExtrema(sphere, extrema);
  205. return sphere;
  206. }