image.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. ...color,
  136. ...marker,
  137. ...overpaint,
  138. ...transparency,
  139. ...material,
  140. ...clipping,
  141. ...transform,
  142. ...BaseGeometry.createValues(props, counts),
  143. aPosition: image.cornerBuffer,
  144. aUv: ValueCell.create(QuadUvs),
  145. elements: ValueCell.create(QuadIndices),
  146. // aGroup is used as a vertex index here, group id is in tGroupTex
  147. aGroup: ValueCell.create(fillSerial(new Float32Array(4))),
  148. boundingSphere: ValueCell.create(boundingSphere),
  149. invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
  150. uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
  151. dInterpolation: ValueCell.create(props.interpolation),
  152. uImageTexDim: image.imageTextureDim,
  153. tImageTex: image.imageTexture,
  154. tGroupTex: image.groupTexture,
  155. };
  156. }
  157. function createValuesSimple(image: Image, props: Partial<PD.Values<Params>>, colorValue: Color, sizeValue: number, transform?: TransformData) {
  158. const s = BaseGeometry.createSimple(colorValue, sizeValue, transform);
  159. const p = { ...PD.getDefaultValues(Params), ...props };
  160. return createValues(image, s.transform, s.locationIterator, s.theme, p);
  161. }
  162. function updateValues(values: ImageValues, props: PD.Values<Params>) {
  163. BaseGeometry.updateValues(values, props);
  164. ValueCell.updateIfChanged(values.dInterpolation, props.interpolation);
  165. }
  166. function updateBoundingSphere(values: ImageValues, image: Image) {
  167. const invariantBoundingSphere = Sphere3D.clone(image.boundingSphere);
  168. const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
  169. if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
  170. ValueCell.update(values.boundingSphere, boundingSphere);
  171. }
  172. if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) {
  173. ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere);
  174. ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere));
  175. }
  176. }
  177. function createRenderableState(props: PD.Values<Params>): RenderableState {
  178. const state = BaseGeometry.createRenderableState(props);
  179. state.opaque = false;
  180. return state;
  181. }
  182. function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
  183. BaseGeometry.updateRenderableState(state, props);
  184. state.opaque = false;
  185. }
  186. }
  187. //
  188. function getBoundingSphere(corners: Float32Array) {
  189. const center = Vec3();
  190. const extrema: Vec3[] = [];
  191. for (let i = 0, il = corners.length; i < il; i += 3) {
  192. const e = Vec3.fromArray(Vec3(), corners, i);
  193. extrema.push(e);
  194. Vec3.add(center, center, e);
  195. }
  196. Vec3.scale(center, center, 1 / (corners.length / 3));
  197. let radius = 0;
  198. for (const e of extrema) {
  199. const d = Vec3.distance(center, e);
  200. if (d > radius) radius = d;
  201. }
  202. const sphere = Sphere3D.create(center, radius);
  203. Sphere3D.setExtrema(sphere, extrema);
  204. return sphere;
  205. }