shape.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Color } from '../../mol-util/color';
  7. import { UUID } from '../../mol-util';
  8. import { OrderedSet } from '../../mol-data/int';
  9. import { Geometry } from '../../mol-geo/geometry/geometry';
  10. import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
  11. import { Sphere3D } from '../../mol-math/geometry';
  12. import { CentroidHelper } from '../../mol-math/geometry/centroid-helper';
  13. import { GroupMapping } from '../../mol-geo/util';
  14. import { ShapeGroupSizeTheme } from '../../mol-theme/size/shape-group';
  15. import { ShapeGroupColorTheme } from '../../mol-theme/color/shape-group';
  16. import { Theme } from '../../mol-theme/theme';
  17. import { TransformData, createTransform as _createTransform } from '../../mol-geo/geometry/transform-data';
  18. import { createRenderObject as _createRenderObject, getNextMaterialId } from '../../mol-gl/render-object';
  19. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  20. import { LocationIterator } from '../../mol-geo/util/location-iterator';
  21. export interface Shape<G extends Geometry = Geometry> {
  22. /** A uuid to identify a shape object */
  23. readonly id: UUID
  24. /** A name to describe the shape */
  25. readonly name: string
  26. /** The data used to create the shape */
  27. readonly sourceData: unknown
  28. /** The geometry of the shape, e.g. `Mesh` or `Lines` */
  29. readonly geometry: G
  30. /** An array of transformation matrices to describe multiple instances of the geometry */
  31. readonly transforms: Mat4[]
  32. /** Number of groups in the geometry */
  33. readonly groupCount: number
  34. /** Get color for a given group */
  35. getColor(groupId: number, instanceId: number): Color
  36. /** Get size for a given group */
  37. getSize(groupId: number, instanceId: number): number
  38. /** Get label for a given group */
  39. getLabel(groupId: number, instanceId: number): string
  40. }
  41. export namespace Shape {
  42. export function create<G extends Geometry>(name: string, sourceData: unknown, geometry: G, getColor: Shape['getColor'], getSize: Shape['getSize'], getLabel: Shape['getLabel'], transforms?: Mat4[]): Shape<G> {
  43. return {
  44. id: UUID.create22(),
  45. name,
  46. sourceData,
  47. geometry,
  48. transforms: transforms || [Mat4.identity()],
  49. get groupCount() { return Geometry.getGroupCount(geometry); },
  50. getColor,
  51. getSize,
  52. getLabel
  53. };
  54. }
  55. export function getTheme(shape: Shape): Theme {
  56. return {
  57. color: ShapeGroupColorTheme({ shape }, {}),
  58. size: ShapeGroupSizeTheme({ shape }, {})
  59. };
  60. }
  61. export function groupIterator(shape: Shape): LocationIterator {
  62. const instanceCount = shape.transforms.length;
  63. const location = ShapeGroup.Location(shape);
  64. const getLocation = (groupIndex: number, instanceIndex: number) => {
  65. location.group = groupIndex;
  66. location.instance = instanceIndex;
  67. return location;
  68. };
  69. return LocationIterator(shape.groupCount, instanceCount, getLocation);
  70. }
  71. export function createTransform(transforms: Mat4[], transformData?: TransformData) {
  72. const transformArray = transformData && transformData.aTransform.ref.value.length >= transforms.length * 16 ? transformData.aTransform.ref.value : new Float32Array(transforms.length * 16);
  73. for (let i = 0, il = transforms.length; i < il; ++i) {
  74. Mat4.toArray(transforms[i], transformArray, i * 16);
  75. }
  76. return _createTransform(transformArray, transforms.length, transformData);
  77. }
  78. export function createRenderObject<G extends Geometry>(shape: Shape<G>, props: PD.Values<Geometry.Params<G>>) {
  79. props;
  80. const theme = Shape.getTheme(shape);
  81. const utils = Geometry.getUtils(shape.geometry);
  82. const materialId = getNextMaterialId();
  83. const locationIt = groupIterator(shape);
  84. const transform = Shape.createTransform(shape.transforms);
  85. const values = utils.createValues(shape.geometry, transform, locationIt, theme, props);
  86. const state = utils.createRenderableState(props);
  87. return _createRenderObject(shape.geometry.kind, values, state, materialId);
  88. }
  89. export interface Loci { readonly kind: 'shape-loci', readonly shape: Shape }
  90. export function Loci(shape: Shape): Loci { return { kind: 'shape-loci', shape }; }
  91. export function isLoci(x: any): x is Loci { return !!x && x.kind === 'shape-loci'; }
  92. export function areLociEqual(a: Loci, b: Loci) { return a.shape === b.shape; }
  93. export function isLociEmpty(loci: Loci) { return loci.shape.groupCount === 0; }
  94. }
  95. export namespace ShapeGroup {
  96. export interface Location {
  97. readonly kind: 'group-location'
  98. shape: Shape
  99. group: number
  100. instance: number
  101. }
  102. export function Location(shape?: Shape, group = 0, instance = 0): Location {
  103. return { kind: 'group-location', shape: shape!, group, instance };
  104. }
  105. export function isLocation(x: any): x is Location {
  106. return !!x && x.kind === 'group-location';
  107. }
  108. export interface Loci {
  109. readonly kind: 'group-loci',
  110. readonly shape: Shape,
  111. readonly groups: ReadonlyArray<{
  112. readonly ids: OrderedSet<number>
  113. readonly instance: number
  114. }>
  115. }
  116. export function Loci(shape: Shape, groups: Loci['groups']): Loci {
  117. return { kind: 'group-loci', shape, groups: groups as Loci['groups'] };
  118. }
  119. export function isLoci(x: any): x is Loci {
  120. return !!x && x.kind === 'group-loci';
  121. }
  122. export function areLociEqual(a: Loci, b: Loci) {
  123. if (a.shape !== b.shape) return false;
  124. if (a.groups.length !== b.groups.length) return false;
  125. for (let i = 0, il = a.groups.length; i < il; ++i) {
  126. const { ids: idsA, instance: instanceA } = a.groups[i];
  127. const { ids: idsB, instance: instanceB } = b.groups[i];
  128. if (instanceA !== instanceB) return false;
  129. if (!OrderedSet.areEqual(idsA, idsB)) return false;
  130. }
  131. return true;
  132. }
  133. export function isLociEmpty(loci: Loci) {
  134. return size(loci) === 0 ? true : false;
  135. }
  136. export function size(loci: Loci) {
  137. let size = 0;
  138. for (const group of loci.groups) {
  139. size += OrderedSet.size(group.ids);
  140. }
  141. return size;
  142. }
  143. const sphereHelper = new CentroidHelper(), tmpPos = Vec3.zero();
  144. function sphereHelperInclude(groups: Loci['groups'], mapping: GroupMapping, positions: Float32Array, transforms: Mat4[]) {
  145. const { indices, offsets } = mapping;
  146. for (const { ids, instance } of groups) {
  147. OrderedSet.forEach(ids, v => {
  148. for (let i = offsets[v], il = offsets[v + 1]; i < il; ++i) {
  149. Vec3.fromArray(tmpPos, positions, indices[i] * 3);
  150. Vec3.transformMat4(tmpPos, tmpPos, transforms[instance]);
  151. sphereHelper.includeStep(tmpPos);
  152. }
  153. });
  154. }
  155. }
  156. function sphereHelperRadius(groups: Loci['groups'], mapping: GroupMapping, positions: Float32Array, transforms: Mat4[]) {
  157. const { indices, offsets } = mapping;
  158. for (const { ids, instance } of groups) {
  159. OrderedSet.forEach(ids, v => {
  160. for (let i = offsets[v], il = offsets[v + 1]; i < il; ++i) {
  161. Vec3.fromArray(tmpPos, positions, indices[i] * 3);
  162. Vec3.transformMat4(tmpPos, tmpPos, transforms[instance]);
  163. sphereHelper.radiusStep(tmpPos);
  164. }
  165. });
  166. }
  167. }
  168. export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D) {
  169. if (!boundingSphere) boundingSphere = Sphere3D();
  170. sphereHelper.reset();
  171. let padding = 0;
  172. const { geometry, transforms } = loci.shape;
  173. if (geometry.kind === 'mesh' || geometry.kind === 'points') {
  174. const positions = geometry.kind === 'mesh'
  175. ? geometry.vertexBuffer.ref.value
  176. : geometry.centerBuffer.ref.value;
  177. sphereHelperInclude(loci.groups, geometry.groupMapping, positions, transforms);
  178. sphereHelper.finishedIncludeStep();
  179. sphereHelperRadius(loci.groups, geometry.groupMapping, positions, transforms);
  180. } else if (geometry.kind === 'lines') {
  181. const start = geometry.startBuffer.ref.value;
  182. const end = geometry.endBuffer.ref.value;
  183. sphereHelperInclude(loci.groups, geometry.groupMapping, start, transforms);
  184. sphereHelperInclude(loci.groups, geometry.groupMapping, end, transforms);
  185. sphereHelper.finishedIncludeStep();
  186. sphereHelperRadius(loci.groups, geometry.groupMapping, start, transforms);
  187. sphereHelperRadius(loci.groups, geometry.groupMapping, end, transforms);
  188. } else if (geometry.kind === 'spheres' || geometry.kind === 'text') {
  189. const positions = geometry.centerBuffer.ref.value;
  190. sphereHelperInclude(loci.groups, geometry.groupMapping, positions, transforms);
  191. sphereHelper.finishedIncludeStep();
  192. sphereHelperRadius(loci.groups, geometry.groupMapping, positions, transforms);
  193. for (const { ids, instance } of loci.groups) {
  194. OrderedSet.forEach(ids, v => {
  195. const value = loci.shape.getSize(v, instance);
  196. if (padding < value) padding = value;
  197. });
  198. }
  199. } else {
  200. // use whole shape bounding-sphere for other geometry kinds
  201. return Sphere3D.copy(boundingSphere, geometry.boundingSphere);
  202. }
  203. Vec3.copy(boundingSphere.center, sphereHelper.center);
  204. boundingSphere.radius = Math.sqrt(sphereHelper.radiusSq);
  205. Sphere3D.expand(boundingSphere, boundingSphere, padding);
  206. return boundingSphere;
  207. }
  208. }