|
@@ -0,0 +1,178 @@
|
|
|
+/**
|
|
|
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
|
|
+ *
|
|
|
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
|
|
|
+ */
|
|
|
+
|
|
|
+import { WebGLContext } from '../../mol-gl/webgl/context';
|
|
|
+import Scene from '../../mol-gl/scene';
|
|
|
+import { Camera } from '../camera';
|
|
|
+import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
|
|
+import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
|
|
+import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
|
|
+import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
|
|
+import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
|
|
+import { ColorNames } from '../../mol-util/color/names';
|
|
|
+import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
|
|
+import { Viewport } from '../camera/util';
|
|
|
+import { ValueCell } from '../../mol-util';
|
|
|
+import { Sphere3D } from '../../mol-math/geometry';
|
|
|
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
|
|
+import produce from 'immer';
|
|
|
+import { Shape } from '../../mol-model/shape';
|
|
|
+import { ShapeRepresentation } from '../../mol-repr/shape/representation';
|
|
|
+import { RuntimeContext } from '../../mol-task';
|
|
|
+
|
|
|
+// TODO add scale line/grid
|
|
|
+
|
|
|
+const AxesParams = {
|
|
|
+ ...Mesh.Params,
|
|
|
+ colorX: PD.Color(ColorNames.red),
|
|
|
+ colorY: PD.Color(ColorNames.green),
|
|
|
+ colorZ: PD.Color(ColorNames.blue),
|
|
|
+ scale: PD.Numeric(1, { min: 0.1, max: 2, step: 0.1 }),
|
|
|
+}
|
|
|
+type AxesParams = typeof AxesParams
|
|
|
+type AxesProps = PD.Values<AxesParams>
|
|
|
+
|
|
|
+export const CameraHelperParams = {
|
|
|
+ axes: PD.MappedStatic('on', {
|
|
|
+ on: PD.Group(AxesParams),
|
|
|
+ off: PD.Group({})
|
|
|
+ }, { cycle: true, description: 'Show camera orientation axes' }),
|
|
|
+}
|
|
|
+export type CameraHelperParams = typeof CameraHelperParams
|
|
|
+export type CameraHelperProps = PD.Values<CameraHelperParams>
|
|
|
+
|
|
|
+export class CameraHelper {
|
|
|
+ scene: Scene
|
|
|
+ camera: Camera
|
|
|
+ props: CameraHelperProps = {
|
|
|
+ axes: { name: 'off', params: {} }
|
|
|
+ }
|
|
|
+
|
|
|
+ private renderObject: GraphicsRenderObject | undefined
|
|
|
+
|
|
|
+ constructor(webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
|
|
|
+ this.scene = Scene.create(webgl)
|
|
|
+
|
|
|
+ this.camera = new Camera()
|
|
|
+ Vec3.set(this.camera.up, 0, 1, 0)
|
|
|
+ Vec3.set(this.camera.target, 0, 0, 0)
|
|
|
+
|
|
|
+ this.setProps(props)
|
|
|
+ }
|
|
|
+
|
|
|
+ setProps(props: Partial<CameraHelperProps>) {
|
|
|
+ this.props = produce(this.props, p => {
|
|
|
+ if (props.axes !== undefined) {
|
|
|
+ p.axes.name = props.axes.name
|
|
|
+ if (props.axes.name === 'on') {
|
|
|
+ this.scene.clear()
|
|
|
+ this.renderObject = undefined
|
|
|
+ createAxesRenderObject(props.axes.params).then(renderObject => {
|
|
|
+ this.renderObject = renderObject
|
|
|
+ this.scene.add(this.renderObject)
|
|
|
+ this.scene.commit()
|
|
|
+ })
|
|
|
+
|
|
|
+ Vec3.set(this.camera.position, 0, 0, props.axes.params.scale * 200)
|
|
|
+ Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up)
|
|
|
+
|
|
|
+ p.axes.params = { ...props.axes.params }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ get isEnabled() {
|
|
|
+ return this.props.axes.name === 'on'
|
|
|
+ }
|
|
|
+
|
|
|
+ update(camera: Camera) {
|
|
|
+ if (!this.renderObject) return
|
|
|
+
|
|
|
+ updateCamera(this.camera, camera.viewport)
|
|
|
+
|
|
|
+ const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4
|
|
|
+ Mat4.extractRotation(m, camera.view)
|
|
|
+
|
|
|
+ const r = this.renderObject.values.boundingSphere.ref.value.radius
|
|
|
+ Mat4.setTranslation(m, Vec3.create(
|
|
|
+ -camera.viewport.width / 2 + r,
|
|
|
+ -camera.viewport.height / 2 + r,
|
|
|
+ 0
|
|
|
+ ))
|
|
|
+
|
|
|
+ ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value)
|
|
|
+ this.scene.update([this.renderObject], true)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function updateCamera(camera: Camera, viewport: Viewport) {
|
|
|
+ const { near, far } = camera
|
|
|
+
|
|
|
+ const fullLeft = -(viewport.width - viewport.x) / 2
|
|
|
+ const fullRight = (viewport.width - viewport.x) / 2
|
|
|
+ const fullTop = (viewport.height - viewport.y) / 2
|
|
|
+ const fullBottom = -(viewport.height - viewport.y) / 2
|
|
|
+
|
|
|
+ const dx = (fullRight - fullLeft) / 2
|
|
|
+ const dy = (fullTop - fullBottom) / 2
|
|
|
+ const cx = (fullRight + fullLeft) / 2
|
|
|
+ const cy = (fullTop + fullBottom) / 2
|
|
|
+
|
|
|
+ const left = cx - dx
|
|
|
+ const right = cx + dx
|
|
|
+ const top = cy + dy
|
|
|
+ const bottom = cy - dy
|
|
|
+
|
|
|
+ Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
|
|
|
+}
|
|
|
+
|
|
|
+function createAxesMesh(scale: number, mesh?: Mesh) {
|
|
|
+ const state = MeshBuilder.createState(512, 256, mesh)
|
|
|
+ const radius = 0.05 * scale
|
|
|
+ const x = Vec3.scale(Vec3(), Vec3.unitX, scale)
|
|
|
+ const y = Vec3.scale(Vec3(), Vec3.unitY, scale)
|
|
|
+ const z = Vec3.scale(Vec3(), Vec3.unitZ, scale)
|
|
|
+ const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 }
|
|
|
+
|
|
|
+ state.currentGroup = 0
|
|
|
+ addSphere(state, Vec3.origin, radius, 2)
|
|
|
+
|
|
|
+ state.currentGroup = 1
|
|
|
+ addSphere(state, x, radius, 2)
|
|
|
+ addCylinder(state, Vec3.origin, x, 1, cylinderProps)
|
|
|
+
|
|
|
+ state.currentGroup = 2
|
|
|
+ addSphere(state, y, radius, 2)
|
|
|
+ addCylinder(state, Vec3.origin, y, 1, cylinderProps)
|
|
|
+
|
|
|
+ state.currentGroup = 3
|
|
|
+ addSphere(state, z, radius, 2)
|
|
|
+ addCylinder(state, Vec3.origin, z, 1, cylinderProps)
|
|
|
+
|
|
|
+ return MeshBuilder.getMesh(state)
|
|
|
+}
|
|
|
+
|
|
|
+function getAxesShape(ctx: RuntimeContext, data: {}, props: AxesProps, shape?: Shape<Mesh>) {
|
|
|
+ const scale = 100 * props.scale
|
|
|
+ const mesh = createAxesMesh(scale, shape?.geometry)
|
|
|
+ mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4))
|
|
|
+ const getColor = (groupId: number) => {
|
|
|
+ switch (groupId) {
|
|
|
+ case 1: return props.colorX
|
|
|
+ case 2: return props.colorY
|
|
|
+ case 3: return props.colorZ
|
|
|
+ default: return ColorNames.grey
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return Shape.create('axes', {}, mesh, getColor, () => 1, () => '')
|
|
|
+}
|
|
|
+
|
|
|
+async function createAxesRenderObject(props: AxesProps) {
|
|
|
+ const repr = ShapeRepresentation(getAxesShape, Mesh.Utils)
|
|
|
+ await repr.createOrUpdate(props, {}).run()
|
|
|
+ return repr.renderObjects[0]
|
|
|
+}
|