handle-helper.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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 { WebGLContext } from '../../mol-gl/webgl/context';
  7. import { Scene } from '../../mol-gl/scene';
  8. import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
  9. import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
  10. import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
  11. import { GraphicsRenderObject } from '../../mol-gl/render-object';
  12. import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
  13. import { ColorNames } from '../../mol-util/color/names';
  14. import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
  15. import { ValueCell } from '../../mol-util';
  16. import { Sphere3D } from '../../mol-math/geometry';
  17. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  18. import produce from 'immer';
  19. import { Shape } from '../../mol-model/shape';
  20. import { PickingId } from '../../mol-geo/geometry/picking';
  21. import { Camera } from '../camera';
  22. import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
  23. import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
  24. import { Visual } from '../../mol-repr/visual';
  25. import { Interval } from '../../mol-data/int';
  26. const HandleParams = {
  27. ...Mesh.Params,
  28. alpha: { ...Mesh.Params.alpha, defaultValue: 1 },
  29. ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
  30. colorX: PD.Color(ColorNames.red, { isEssential: true }),
  31. colorY: PD.Color(ColorNames.green, { isEssential: true }),
  32. colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
  33. scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
  34. };
  35. type HandleParams = typeof HandleParams
  36. type HandleProps = PD.Values<HandleParams>
  37. export const HandleHelperParams = {
  38. handle: PD.MappedStatic('off', {
  39. on: PD.Group(HandleParams),
  40. off: PD.Group({})
  41. }, { cycle: true, description: 'Show handle tool' }),
  42. };
  43. export type HandleHelperParams = typeof HandleHelperParams
  44. export type HandleHelperProps = PD.Values<HandleHelperParams>
  45. export class HandleHelper {
  46. scene: Scene
  47. props: HandleHelperProps = {
  48. handle: { name: 'off', params: {} }
  49. }
  50. private renderObject: GraphicsRenderObject | undefined
  51. private _transform = Mat4();
  52. getBoundingSphere(out: Sphere3D, instanceId: number) {
  53. if (this.renderObject) {
  54. Sphere3D.copy(out, this.renderObject.values.invariantBoundingSphere.ref.value);
  55. Mat4.fromArray(this._transform, this.renderObject.values.aTransform.ref.value, instanceId * 16);
  56. Sphere3D.transform(out, out, this._transform);
  57. }
  58. return out;
  59. }
  60. setProps(props: Partial<HandleHelperProps>) {
  61. this.props = produce(this.props, p => {
  62. if (props.handle !== undefined) {
  63. p.handle.name = props.handle.name;
  64. if (props.handle.name === 'on') {
  65. this.scene.clear();
  66. const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
  67. this.renderObject = createHandleRenderObject(params);
  68. this.renderObject.state.noClip = true;
  69. this.scene.add(this.renderObject);
  70. this.scene.commit();
  71. p.handle.params = { ...props.handle.params };
  72. }
  73. }
  74. });
  75. }
  76. get isEnabled() {
  77. return this.props.handle.name === 'on';
  78. }
  79. // TODO could be a lists of position/rotation if we want to show more than one handle tool,
  80. // they would be distingishable by their instanceId
  81. update(camera: Camera, position: Vec3, rotation: Mat3) {
  82. if (!this.renderObject) return;
  83. Mat4.setTranslation(this.renderObject.values.aTransform.ref.value as unknown as Mat4, position);
  84. Mat4.fromMat3(this.renderObject.values.aTransform.ref.value as unknown as Mat4, rotation);
  85. // TODO make invariant to camera scaling by adjusting renderObject transform
  86. ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value);
  87. this.scene.update([this.renderObject], true);
  88. }
  89. getLoci(pickingId: PickingId) {
  90. const { objectId, groupId, instanceId } = pickingId;
  91. if (!this.renderObject || objectId !== this.renderObject.id) return EmptyLoci;
  92. return HandleLoci(this, groupId, instanceId);
  93. }
  94. private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
  95. if (!this.renderObject) return false;
  96. if (!isHandleLoci(loci)) return false;
  97. let changed = false;
  98. const groupCount = this.renderObject.values.uGroupCount.ref.value;
  99. const { elements } = loci;
  100. for (const { groupId, instanceId } of elements) {
  101. const idx = instanceId * groupCount + groupId;
  102. if (apply(Interval.ofSingleton(idx))) changed = true;
  103. }
  104. return changed;
  105. }
  106. mark(loci: Loci, action: MarkerAction) {
  107. if (!MarkerActions.is(MarkerActions.Highlighting, action)) return false;
  108. if (!isHandleLoci(loci)) return false;
  109. if (loci.data !== this) return false;
  110. return Visual.mark(this.renderObject, loci, action, this.eachGroup);
  111. }
  112. constructor(private webgl: WebGLContext, props: Partial<HandleHelperProps> = {}) {
  113. this.scene = Scene.create(webgl);
  114. this.setProps(props);
  115. }
  116. }
  117. function createHandleMesh(scale: number, mesh?: Mesh) {
  118. const state = MeshBuilder.createState(512, 256, mesh);
  119. const radius = 0.05 * scale;
  120. const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
  121. const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
  122. const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
  123. const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
  124. state.currentGroup = HandleGroup.TranslateScreenXY;
  125. addSphere(state, Vec3.origin, radius * 3, 2);
  126. state.currentGroup = HandleGroup.TranslateObjectX;
  127. addSphere(state, x, radius, 2);
  128. addCylinder(state, Vec3.origin, x, 1, cylinderProps);
  129. state.currentGroup = HandleGroup.TranslateObjectY;
  130. addSphere(state, y, radius, 2);
  131. addCylinder(state, Vec3.origin, y, 1, cylinderProps);
  132. state.currentGroup = HandleGroup.TranslateObjectZ;
  133. addSphere(state, z, radius, 2);
  134. addCylinder(state, Vec3.origin, z, 1, cylinderProps);
  135. // TODO add more helper geometries for the other HandleGroup options
  136. // TODO add props to create subset of geometries
  137. return MeshBuilder.getMesh(state);
  138. }
  139. export const HandleGroup = {
  140. None: 0,
  141. TranslateScreenXY: 1,
  142. // TranslateScreenZ: 2,
  143. TranslateObjectX: 3,
  144. TranslateObjectY: 4,
  145. TranslateObjectZ: 5,
  146. // TranslateObjectXY: 6,
  147. // TranslateObjectXZ: 7,
  148. // TranslateObjectYZ: 8,
  149. // RotateScreenZ: 9,
  150. // RotateObjectX: 10,
  151. // RotateObjectY: 11,
  152. // RotateObjectZ: 12,
  153. } as const;
  154. function HandleLoci(handleHelper: HandleHelper, groupId: number, instanceId: number) {
  155. return DataLoci('handle', handleHelper, [{ groupId, instanceId }],
  156. (boundingSphere: Sphere3D) => handleHelper.getBoundingSphere(boundingSphere, instanceId),
  157. () => `Handle Helper | Group Id ${groupId} | Instance Id ${instanceId}`);
  158. }
  159. export type HandleLoci = ReturnType<typeof HandleLoci>
  160. export function isHandleLoci(x: Loci): x is HandleLoci {
  161. return x.kind === 'data-loci' && x.tag === 'handle';
  162. }
  163. function getHandleShape(props: HandleProps, shape?: Shape<Mesh>) {
  164. const scale = 10 * props.scale;
  165. const mesh = createHandleMesh(scale, shape?.geometry);
  166. mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
  167. const getColor = (groupId: number) => {
  168. switch (groupId) {
  169. case HandleGroup.TranslateObjectX: return props.colorX;
  170. case HandleGroup.TranslateObjectY: return props.colorY;
  171. case HandleGroup.TranslateObjectZ: return props.colorZ;
  172. default: return ColorNames.grey;
  173. }
  174. };
  175. return Shape.create('handle', {}, mesh, getColor, () => 1, () => '');
  176. }
  177. function createHandleRenderObject(props: HandleProps) {
  178. const shape = getHandleShape(props);
  179. return Shape.createRenderObject(shape, props);
  180. }