camera-helper.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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 { Camera, ICamera } from '../camera';
  9. import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
  10. import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
  11. import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
  12. import { GraphicsRenderObject } from '../../mol-gl/render-object';
  13. import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
  14. import { ColorNames } from '../../mol-util/color/names';
  15. import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
  16. import { Viewport } from '../camera/util';
  17. import { Sphere3D } from '../../mol-math/geometry';
  18. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  19. import produce from 'immer';
  20. import { Shape } from '../../mol-model/shape';
  21. // TODO add scale line/grid
  22. const AxesParams = {
  23. ...Mesh.Params,
  24. alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
  25. ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
  26. colorX: PD.Color(ColorNames.red, { isEssential: true }),
  27. colorY: PD.Color(ColorNames.green, { isEssential: true }),
  28. colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
  29. scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
  30. };
  31. type AxesParams = typeof AxesParams
  32. type AxesProps = PD.Values<AxesParams>
  33. export const CameraHelperParams = {
  34. axes: PD.MappedStatic('on', {
  35. on: PD.Group(AxesParams),
  36. off: PD.Group({})
  37. }, { cycle: true, description: 'Show camera orientation axes' }),
  38. };
  39. export type CameraHelperParams = typeof CameraHelperParams
  40. export type CameraHelperProps = PD.Values<CameraHelperParams>
  41. export class CameraHelper {
  42. scene: Scene
  43. camera: Camera
  44. props: CameraHelperProps = {
  45. axes: { name: 'off', params: {} }
  46. }
  47. private renderObject: GraphicsRenderObject | undefined
  48. constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
  49. this.scene = Scene.create(webgl);
  50. this.camera = new Camera();
  51. Vec3.set(this.camera.up, 0, 1, 0);
  52. Vec3.set(this.camera.target, 0, 0, 0);
  53. this.setProps(props);
  54. }
  55. setProps(props: Partial<CameraHelperProps>) {
  56. this.props = produce(this.props, p => {
  57. if (props.axes !== undefined) {
  58. p.axes.name = props.axes.name;
  59. if (props.axes.name === 'on') {
  60. this.scene.clear();
  61. const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
  62. this.renderObject = createAxesRenderObject(params);
  63. this.renderObject.state.noClip = true;
  64. this.scene.add(this.renderObject);
  65. this.scene.commit();
  66. Vec3.set(this.camera.position, 0, 0, params.scale * 200);
  67. Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up);
  68. p.axes.params = { ...props.axes.params };
  69. }
  70. }
  71. });
  72. }
  73. get isEnabled() {
  74. return this.props.axes.name === 'on';
  75. }
  76. update(camera: ICamera) {
  77. if (!this.renderObject) return;
  78. updateCamera(this.camera, camera.viewport, camera.viewOffset);
  79. Mat4.extractRotation(this.scene.view, camera.view);
  80. const r = this.renderObject.values.boundingSphere.ref.value.radius;
  81. Mat4.setTranslation(this.scene.view, Vec3.create(
  82. -camera.viewport.width / 2 + r,
  83. -camera.viewport.height / 2 + r,
  84. 0
  85. ));
  86. }
  87. }
  88. function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) {
  89. const { near, far } = camera;
  90. const fullLeft = -viewport.width / 2;
  91. const fullRight = viewport.width / 2;
  92. const fullTop = viewport.height / 2;
  93. const fullBottom = -viewport.height / 2;
  94. const dx = (fullRight - fullLeft) / 2;
  95. const dy = (fullTop - fullBottom) / 2;
  96. const cx = (fullRight + fullLeft) / 2;
  97. const cy = (fullTop + fullBottom) / 2;
  98. let left = cx - dx;
  99. let right = cx + dx;
  100. let top = cy + dy;
  101. let bottom = cy - dy;
  102. if (viewOffset.enabled) {
  103. const scaleW = (fullRight - fullLeft) / viewOffset.width;
  104. const scaleH = (fullTop - fullBottom) / viewOffset.height;
  105. left += scaleW * viewOffset.offsetX;
  106. right = left + scaleW * viewOffset.width;
  107. top -= scaleH * viewOffset.offsetY;
  108. bottom = top - scaleH * viewOffset.height;
  109. }
  110. Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
  111. }
  112. function createAxesMesh(scale: number, mesh?: Mesh) {
  113. const state = MeshBuilder.createState(512, 256, mesh);
  114. const radius = 0.05 * scale;
  115. const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
  116. const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
  117. const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
  118. const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
  119. state.currentGroup = 0;
  120. addSphere(state, Vec3.origin, radius, 2);
  121. state.currentGroup = 1;
  122. addSphere(state, x, radius, 2);
  123. addCylinder(state, Vec3.origin, x, 1, cylinderProps);
  124. state.currentGroup = 2;
  125. addSphere(state, y, radius, 2);
  126. addCylinder(state, Vec3.origin, y, 1, cylinderProps);
  127. state.currentGroup = 3;
  128. addSphere(state, z, radius, 2);
  129. addCylinder(state, Vec3.origin, z, 1, cylinderProps);
  130. return MeshBuilder.getMesh(state);
  131. }
  132. function getAxesShape(props: AxesProps, shape?: Shape<Mesh>) {
  133. const scale = 100 * props.scale;
  134. const mesh = createAxesMesh(scale, shape?.geometry);
  135. mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
  136. const getColor = (groupId: number) => {
  137. switch (groupId) {
  138. case 1: return props.colorX;
  139. case 2: return props.colorY;
  140. case 3: return props.colorZ;
  141. default: return ColorNames.grey;
  142. }
  143. };
  144. return Shape.create('axes', {}, mesh, getColor, () => 1, () => '');
  145. }
  146. function createAxesRenderObject(props: AxesProps) {
  147. const shape = getAxesShape(props);
  148. return Shape.createRenderObject(shape, props);
  149. }