image.ts 9.3 KB

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