text.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  7. import { ValueCell } from '../../../mol-util';
  8. import { GeometryUtils } from '../geometry';
  9. import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
  10. import { TransformData } from '../transform-data';
  11. import { Theme } from '../../../mol-theme/theme';
  12. import { createColors } from '../color-data';
  13. import { createSizes, getMaxSize } from '../size-data';
  14. import { createMarkers } from '../marker-data';
  15. import { ColorNames } from '../../../mol-util/color/names';
  16. import { Sphere3D } from '../../../mol-math/geometry';
  17. import { TextureImage, createTextureImage, calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
  18. import { TextValues } from '../../../mol-gl/renderable/text';
  19. import { Color } from '../../../mol-util/color';
  20. import { Vec3, Vec4 } from '../../../mol-math/linear-algebra';
  21. import { FontAtlasParams } from './font-atlas';
  22. import { RenderableState } from '../../../mol-gl/renderable';
  23. import { clamp } from '../../../mol-math/interpolate';
  24. import { createRenderObject as _createRenderObject } from '../../../mol-gl/render-object';
  25. import { BaseGeometry } from '../base';
  26. import { createEmptyOverpaint } from '../overpaint-data';
  27. import { createEmptyTransparency } from '../transparency-data';
  28. import { hashFnv32a } from '../../../mol-data/util';
  29. import { GroupMapping, createGroupMapping } from '../../util';
  30. import { createEmptyClipping } from '../clipping-data';
  31. import { createEmptySubstance } from '../substance-data';
  32. type TextAttachment = (
  33. 'bottom-left' | 'bottom-center' | 'bottom-right' |
  34. 'middle-left' | 'middle-center' | 'middle-right' |
  35. 'top-left' | 'top-center' | 'top-right'
  36. )
  37. /** Text */
  38. export interface Text {
  39. readonly kind: 'text',
  40. /** Number of characters in the text */
  41. charCount: number,
  42. /** Font Atlas */
  43. readonly fontTexture: ValueCell<TextureImage<Uint8Array>>,
  44. /** Center buffer as array of xyz values wrapped in a value cell */
  45. readonly centerBuffer: ValueCell<Float32Array>,
  46. /** Mapping buffer as array of xy values wrapped in a value cell */
  47. readonly mappingBuffer: ValueCell<Float32Array>,
  48. /** Depth buffer as array of z values wrapped in a value cell */
  49. readonly depthBuffer: ValueCell<Float32Array>,
  50. /** Index buffer as array of center index triplets wrapped in a value cell */
  51. readonly indexBuffer: ValueCell<Uint32Array>,
  52. /** Group buffer as array of group ids for each vertex wrapped in a value cell */
  53. readonly groupBuffer: ValueCell<Float32Array>,
  54. /** Texture coordinates buffer as array of uv values wrapped in a value cell */
  55. readonly tcoordBuffer: ValueCell<Float32Array>,
  56. /** Bounding sphere of the text */
  57. readonly boundingSphere: Sphere3D
  58. /** Maps group ids to text indices */
  59. readonly groupMapping: GroupMapping
  60. setBoundingSphere(boundingSphere: Sphere3D): void
  61. }
  62. export namespace Text {
  63. export function create(fontTexture: TextureImage<Uint8Array>, centers: Float32Array, mappings: Float32Array, depths: Float32Array, indices: Uint32Array, groups: Float32Array, tcoords: Float32Array, charCount: number, text?: Text): Text {
  64. return text ?
  65. update(fontTexture, centers, mappings, depths, indices, groups, tcoords, charCount, text) :
  66. fromData(fontTexture, centers, mappings, depths, indices, groups, tcoords, charCount);
  67. }
  68. export function createEmpty(text?: Text): Text {
  69. const ft = text ? text.fontTexture.ref.value : createTextureImage(0, 1, Uint8Array);
  70. const cb = text ? text.centerBuffer.ref.value : new Float32Array(0);
  71. const mb = text ? text.mappingBuffer.ref.value : new Float32Array(0);
  72. const db = text ? text.depthBuffer.ref.value : new Float32Array(0);
  73. const ib = text ? text.indexBuffer.ref.value : new Uint32Array(0);
  74. const gb = text ? text.groupBuffer.ref.value : new Float32Array(0);
  75. const tb = text ? text.tcoordBuffer.ref.value : new Float32Array(0);
  76. return create(ft, cb, mb, db, ib, gb, tb, 0, text);
  77. }
  78. function hashCode(text: Text) {
  79. return hashFnv32a([
  80. text.charCount, text.fontTexture.ref.version,
  81. text.centerBuffer.ref.version, text.mappingBuffer.ref.version,
  82. text.depthBuffer.ref.version, text.indexBuffer.ref.version,
  83. text.groupBuffer.ref.version, text.tcoordBuffer.ref.version
  84. ]);
  85. }
  86. function fromData(fontTexture: TextureImage<Uint8Array>, centers: Float32Array, mappings: Float32Array, depths: Float32Array, indices: Uint32Array, groups: Float32Array, tcoords: Float32Array, charCount: number): Text {
  87. const boundingSphere = Sphere3D();
  88. let groupMapping: GroupMapping;
  89. let currentHash = -1;
  90. let currentGroup = -1;
  91. const text = {
  92. kind: 'text' as const,
  93. charCount,
  94. fontTexture: ValueCell.create(fontTexture),
  95. centerBuffer: ValueCell.create(centers),
  96. mappingBuffer: ValueCell.create(mappings),
  97. depthBuffer: ValueCell.create(depths),
  98. indexBuffer: ValueCell.create(indices),
  99. groupBuffer: ValueCell.create(groups),
  100. tcoordBuffer: ValueCell.create(tcoords),
  101. get boundingSphere() {
  102. const newHash = hashCode(text);
  103. if (newHash !== currentHash) {
  104. const b = calculateInvariantBoundingSphere(text.centerBuffer.ref.value, text.charCount * 4, 4);
  105. Sphere3D.copy(boundingSphere, b);
  106. currentHash = newHash;
  107. }
  108. return boundingSphere;
  109. },
  110. get groupMapping() {
  111. if (text.groupBuffer.ref.version !== currentGroup) {
  112. groupMapping = createGroupMapping(text.groupBuffer.ref.value, text.charCount, 4);
  113. currentGroup = text.groupBuffer.ref.version;
  114. }
  115. return groupMapping;
  116. },
  117. setBoundingSphere(sphere: Sphere3D) {
  118. Sphere3D.copy(boundingSphere, sphere);
  119. currentHash = hashCode(text);
  120. }
  121. };
  122. return text;
  123. }
  124. function update(fontTexture: TextureImage<Uint8Array>, centers: Float32Array, mappings: Float32Array, depths: Float32Array, indices: Uint32Array, groups: Float32Array, tcoords: Float32Array, charCount: number, text: Text) {
  125. text.charCount = charCount;
  126. ValueCell.update(text.fontTexture, fontTexture);
  127. ValueCell.update(text.centerBuffer, centers);
  128. ValueCell.update(text.mappingBuffer, mappings);
  129. ValueCell.update(text.depthBuffer, depths);
  130. ValueCell.update(text.indexBuffer, indices);
  131. ValueCell.update(text.groupBuffer, groups);
  132. ValueCell.update(text.tcoordBuffer, tcoords);
  133. return text;
  134. }
  135. export const Params = {
  136. ...BaseGeometry.Params,
  137. ...FontAtlasParams,
  138. sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
  139. borderWidth: PD.Numeric(0, { min: 0, max: 0.5, step: 0.01 }),
  140. borderColor: PD.Color(ColorNames.grey),
  141. offsetX: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }),
  142. offsetY: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }),
  143. offsetZ: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }),
  144. background: PD.Boolean(false),
  145. backgroundMargin: PD.Numeric(0.2, { min: 0, max: 1, step: 0.01 }),
  146. backgroundColor: PD.Color(ColorNames.grey),
  147. backgroundOpacity: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
  148. tether: PD.Boolean(false),
  149. tetherLength: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }),
  150. tetherBaseWidth: PD.Numeric(0.3, { min: 0, max: 1, step: 0.01 }),
  151. attachment: PD.Select('middle-center', [
  152. ['bottom-left', 'bottom-left'], ['bottom-center', 'bottom-center'], ['bottom-right', 'bottom-right'],
  153. ['middle-left', 'middle-left'], ['middle-center', 'middle-center'], ['middle-right', 'middle-right'],
  154. ['top-left', 'top-left'], ['top-center', 'top-center'], ['top-right', 'top-right'],
  155. ] as [TextAttachment, string][]),
  156. };
  157. export type Params = typeof Params
  158. export const Utils: GeometryUtils<Text, Params> = {
  159. Params,
  160. createEmpty,
  161. createValues,
  162. createValuesSimple,
  163. updateValues,
  164. updateBoundingSphere,
  165. createRenderableState,
  166. updateRenderableState,
  167. createPositionIterator
  168. };
  169. function createPositionIterator(text: Text, transform: TransformData): LocationIterator {
  170. const groupCount = text.charCount * 4;
  171. const instanceCount = transform.instanceCount.ref.value;
  172. const location = PositionLocation();
  173. const p = location.position;
  174. const v = text.centerBuffer.ref.value;
  175. const m = transform.aTransform.ref.value;
  176. const getLocation = (groupIndex: number, instanceIndex: number) => {
  177. if (instanceIndex < 0) {
  178. Vec3.fromArray(p, v, groupIndex * 3);
  179. } else {
  180. Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16);
  181. }
  182. return location;
  183. };
  184. return LocationIterator(groupCount, instanceCount, 4, getLocation);
  185. }
  186. function createValues(text: Text, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): TextValues {
  187. const { instanceCount, groupCount } = locationIt;
  188. const positionIt = createPositionIterator(text, transform);
  189. const color = createColors(locationIt, positionIt, theme.color);
  190. const size = createSizes(locationIt, theme.size);
  191. const marker = createMarkers(instanceCount * groupCount);
  192. const overpaint = createEmptyOverpaint();
  193. const transparency = createEmptyTransparency();
  194. const substance = createEmptySubstance();
  195. const clipping = createEmptyClipping();
  196. const counts = { drawCount: text.charCount * 2 * 3, vertexCount: text.charCount * 4, groupCount, instanceCount };
  197. const padding = getPadding(text.mappingBuffer.ref.value, text.depthBuffer.ref.value, text.charCount, getMaxSize(size));
  198. const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), text.boundingSphere, padding);
  199. const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
  200. return {
  201. dGeometryType: ValueCell.create('text'),
  202. aPosition: text.centerBuffer,
  203. aMapping: text.mappingBuffer,
  204. aDepth: text.depthBuffer,
  205. aGroup: text.groupBuffer,
  206. elements: text.indexBuffer,
  207. boundingSphere: ValueCell.create(boundingSphere),
  208. invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
  209. uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
  210. ...color,
  211. ...size,
  212. ...marker,
  213. ...overpaint,
  214. ...transparency,
  215. ...substance,
  216. ...clipping,
  217. ...transform,
  218. aTexCoord: text.tcoordBuffer,
  219. tFont: text.fontTexture,
  220. padding: ValueCell.create(padding),
  221. ...BaseGeometry.createValues(props, counts),
  222. uSizeFactor: ValueCell.create(props.sizeFactor),
  223. uBorderWidth: ValueCell.create(clamp(props.borderWidth, 0, 0.5)),
  224. uBorderColor: ValueCell.create(Color.toArrayNormalized(props.borderColor, Vec3.zero(), 0)),
  225. uOffsetX: ValueCell.create(props.offsetX),
  226. uOffsetY: ValueCell.create(props.offsetY),
  227. uOffsetZ: ValueCell.create(props.offsetZ),
  228. uBackgroundColor: ValueCell.create(Color.toArrayNormalized(props.backgroundColor, Vec3.zero(), 0)),
  229. uBackgroundOpacity: ValueCell.create(props.backgroundOpacity),
  230. };
  231. }
  232. function createValuesSimple(text: Text, props: Partial<PD.Values<Params>>, colorValue: Color, sizeValue: number, transform?: TransformData) {
  233. const s = BaseGeometry.createSimple(colorValue, sizeValue, transform);
  234. const p = { ...PD.getDefaultValues(Params), ...props };
  235. return createValues(text, s.transform, s.locationIterator, s.theme, p);
  236. }
  237. function updateValues(values: TextValues, props: PD.Values<Params>) {
  238. BaseGeometry.updateValues(values, props);
  239. ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor);
  240. ValueCell.updateIfChanged(values.uBorderWidth, props.borderWidth);
  241. if (Color.fromNormalizedArray(values.uBorderColor.ref.value, 0) !== props.borderColor) {
  242. Color.toArrayNormalized(props.borderColor, values.uBorderColor.ref.value, 0);
  243. ValueCell.update(values.uBorderColor, values.uBorderColor.ref.value);
  244. }
  245. ValueCell.updateIfChanged(values.uOffsetX, props.offsetX);
  246. ValueCell.updateIfChanged(values.uOffsetY, props.offsetY);
  247. ValueCell.updateIfChanged(values.uOffsetZ, props.offsetZ);
  248. if (Color.fromNormalizedArray(values.uBackgroundColor.ref.value, 0) !== props.backgroundColor) {
  249. Color.toArrayNormalized(props.backgroundColor, values.uBackgroundColor.ref.value, 0);
  250. ValueCell.update(values.uBackgroundColor, values.uBackgroundColor.ref.value);
  251. }
  252. ValueCell.updateIfChanged(values.uBackgroundOpacity, props.backgroundOpacity);
  253. }
  254. function updateBoundingSphere(values: TextValues, text: Text) {
  255. const padding = getPadding(values.aMapping.ref.value, values.aDepth.ref.value, text.charCount, getMaxSize(values));
  256. const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), text.boundingSphere, padding);
  257. const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
  258. if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
  259. ValueCell.update(values.boundingSphere, boundingSphere);
  260. }
  261. if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) {
  262. ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere);
  263. ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere));
  264. }
  265. ValueCell.update(values.padding, padding);
  266. }
  267. function createRenderableState(props: PD.Values<Params>): RenderableState {
  268. const state = BaseGeometry.createRenderableState(props);
  269. updateRenderableState(state, props);
  270. return state;
  271. }
  272. function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
  273. BaseGeometry.updateRenderableState(state, props);
  274. state.pickable = false;
  275. state.opaque = false;
  276. state.writeDepth = true;
  277. }
  278. }
  279. function getPadding(mappings: Float32Array, depths: Float32Array, charCount: number, maxSize: number) {
  280. let maxOffset = 0;
  281. let maxDepth = 0;
  282. for (let i = 0, il = charCount * 4; i < il; ++i) {
  283. const i2 = 2 * i;
  284. const ox = Math.abs(mappings[i2]);
  285. if (ox > maxOffset) maxOffset = ox;
  286. const oy = Math.abs(mappings[i2 + 1]);
  287. if (oy > maxOffset) maxOffset = oy;
  288. const d = Math.abs(depths[i]);
  289. if (d > maxDepth) maxDepth = d;
  290. }
  291. // console.log(maxDepth + maxSize, maxDepth, maxSize, maxSize + maxSize * maxOffset, depths)
  292. return Math.max(maxDepth, maxSize + maxSize * maxOffset);
  293. // return maxSize + maxSize * maxOffset + maxDepth
  294. }