Skip to content
Snippets Groups Projects
ngLayerCtrl.component.ts 7.26 KiB
Newer Older
import { ChangeDetectionStrategy, Component, Inject, Input, OnChanges, OnDestroy } from "@angular/core";
import { Store, select } from "@ngrx/store";
Xiao Gui's avatar
Xiao Gui committed
import { isMat4 } from "common/util"
import { CONST } from "common/constants"
Xiao Gui's avatar
Xiao Gui committed
import { Observable } from "rxjs";
import { atlasAppearance, atlasSelection } from "src/state";
import { NehubaViewerUnit, NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba";
Xiao Gui's avatar
Xiao Gui committed
import { getExportNehuba, getShaderFromMeta } from "src/util/fn";
import { MetaV1Schema, isEnclosed } from "src/atlasComponents/sapi/typeV3";
Xiao Gui's avatar
Xiao Gui committed

type Vec4 = [number, number, number, number]
type Mat4 = [Vec4, Vec4, Vec4, Vec4]

export const idMat4: Mat4 = [
  [1, 0, 0, 0],
  [0, 1, 0, 0],
  [0, 0, 1, 0],
  [0, 0, 0, 1],
]
Xiao Gui's avatar
Xiao Gui committed

@Component({
  selector: 'ng-layer-ctl',
  templateUrl: './ngLayerCtrl.template.html',
  styleUrls: [
    './ngLayerCtrl.style.css'
  ],
  changeDetection: ChangeDetectionStrategy.OnPush
})

export class NgLayerCtrlCmp implements OnChanges, OnDestroy{

Xiao Gui's avatar
Xiao Gui committed
  private onDestroyCb: (() => void)[] = []
  private removeLayer: () => void

Xiao Gui's avatar
Xiao Gui committed
  public hideNgTuneCtrl = ''
  public defaultOpacity = 1

Xiao Gui's avatar
Xiao Gui committed
  @Input('ng-layer-ctrl-show')
  public showOpacityCtrl = false

Xiao Gui's avatar
Xiao Gui committed
  @Input('ng-layer-ctl-name')
  name: string

  @Input('ng-layer-ctl-src')
  source: string

  @Input('ng-layer-ctl-shader')
  shader: string

  @Input("ng-layer-ctl-meta")
  meta: MetaV1Schema

Xiao Gui's avatar
Xiao Gui committed
  opacity: number = 1.0
  @Input('ng-layer-ctl-opacity')
  set _opacity(val: number | string) {
    if (typeof val === 'number') {
      this.opacity = val
      return
    }
    this.opacity = Number(val)
  }
  
  transform: Mat4 = idMat4

Xiao Gui's avatar
Xiao Gui committed
  @Input('ng-layer-ctl-transform')
  set _transform(xform: string | Mat4 | number[][]) {
Xiao Gui's avatar
Xiao Gui committed
    const parsedResult = typeof xform === "string"
      ? JSON.parse(xform)
      : xform
    if (!isMat4(xform)) {
      return
    }
Xiao Gui's avatar
Xiao Gui committed
    this.transform = parsedResult as Mat4
  @Input('ng-layer-ctl-info')
  info: Record<string, any>

Xiao Gui's avatar
Xiao Gui committed
  visible: boolean = true
  private viewer: NehubaViewerUnit

Xiao Gui's avatar
Xiao Gui committed
  private exportNehuba: any
  private currentPositionMm: number[]
Xiao Gui's avatar
Xiao Gui committed

Xiao Gui's avatar
Xiao Gui committed
  constructor(
    private store: Store<any>,
    @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit>
  ){
    const sub = nehubaViewer$.subscribe(v => this.viewer = v)
    const navSub = this.store.pipe(
      select(atlasSelection.selectors.navigation)
    ).subscribe(nav => {
      this.currentPositionMm = nav?.position.map(v => v / 1e6)
    })
Xiao Gui's avatar
Xiao Gui committed
    this.onDestroyCb.push(
      () => sub.unsubscribe(),
      () => navSub.unsubscribe(),
Xiao Gui's avatar
Xiao Gui committed

    getExportNehuba().then(exportNehuba => {
      this.exportNehuba = exportNehuba
      this.setOrientation()
    })
Xiao Gui's avatar
Xiao Gui committed
  }

  ngOnDestroy(): void {
    while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()()
    if (this.removeLayer) {
      this.removeLayer()
      this.removeLayer = null
    }
  }

  async ngOnChanges() {
Xiao Gui's avatar
Xiao Gui committed
    if (this.name && this.source) {
      const { name } = this
      if (this.removeLayer) {
        this.removeLayer()
        this.removeLayer = null
      }
Xiao Gui's avatar
Xiao Gui committed
      this.shader = getShaderFromMeta(this.meta)
Xiao Gui's avatar
Xiao Gui committed
      this.store.dispatch(
        atlasAppearance.actions.addCustomLayer({
          customLayer: {
            id: name,
Xiao Gui's avatar
Xiao Gui committed
            legacySpecFlag: 'old',
Xiao Gui's avatar
Xiao Gui committed
            shader: this.shader,
            transform: this.transform,
            clType: 'customlayer/nglayer',
Xiao Gui's avatar
Xiao Gui committed
            source: `precomputed://${this.source}`,
            opacity: this.opacity,
          }
Xiao Gui's avatar
Xiao Gui committed
        })
      )
      this.removeLayer = () => {
        this.store.dispatch(
          atlasAppearance.actions.removeCustomLayer({
            id: name
  setOrientation(): void {
Xiao Gui's avatar
Xiao Gui committed
    const { mat4, quat, vec3 } = this.exportNehuba

    /**
     * glMatrix seems to store the matrix in transposed format
     */
    
    const incM = mat4.transpose(mat4.create(), mat4.fromValues(...this.transform.reduce((acc, curr) => [...acc, ...curr], [])))
    const scale = mat4.getScaling(vec3.create(), incM)
    const scaledM = mat4.scale(mat4.create(), incM, vec3.inverse(vec3.create(), scale))
    const q = mat4.getRotation(quat.create(0), scaledM)

    let position: number[]
    if (this.info) {
      const { scales } = this.info
      const sizeInNm = [0, 1, 2].map(idx => scales[0].size[idx] * scales[0].resolution[idx])
      const start = vec3.transformMat4(vec3.create(), vec3.fromValues(0, 0, 0), incM)
      const end = vec3.transformMat4(vec3.create(), vec3.fromValues(...sizeInNm), incM)
      const final = vec3.add(vec3.create(), start, end)
      vec3.scale(final, final, 0.5)
      position = Array.from(final)
    }
    const enclosed = (this.meta?.bestViewPoints || []).filter(isEnclosed).find(v => v.points.length >= 3)
    if (enclosed) {
      const curr = vec3.fromValues(...this.currentPositionMm)
      const pt1 = vec3.fromValues(...enclosed.points[0].value)
      const pt0 = vec3.fromValues(...enclosed.points[1].value)
      const pt2 = vec3.fromValues(...enclosed.points[2].value)
      vec3.sub(pt1, pt1, pt0)
      vec3.sub(pt2, pt2, pt0)
Xiao Gui's avatar
Xiao Gui committed

      /**
       * pt1 and pt2 now unit vectors going from pt0 to pt1 and pt0 to pt2 respectively
       */
      vec3.normalize(pt1, pt1)
      vec3.normalize(pt2, pt2)

Xiao Gui's avatar
Xiao Gui committed
      /**
       * calculate rotation first
       */
      const z0 = vec3.fromValues(0, 0, 1)
      const cross = vec3.cross(vec3.create(), z0, pt1)
      const w = Math.sqrt(2) + vec3.dot(z0, pt1)
      quat.set(q, ...cross, w)
      quat.normalize(q, q)

      /**
       * curr is now vector going from pt0 to current navigation position
       */
      vec3.sub(curr, curr, pt0)

Xiao Gui's avatar
Xiao Gui committed
      const normal = vec3.cross(vec3.create(), pt1, pt2)
      vec3.normalize(normal, normal)
      vec3.mul(normal, normal, curr)

      const inPlaneDisplacement = vec3.sub(vec3.create(), curr, normal)

      /**
       * check enclosedness
       * also caches the closes point
       */
      const ipdCoord = vec3.add(vec3.create(), pt0, inPlaneDisplacement)
      const allPoints = enclosed.points.map(v => vec3.fromValues(...v.value))
      let sum = 0
      let minDist: number = Number.POSITIVE_INFINITY
      let pos: any
      for (let i = 0; i < allPoints.length; i++) {
        const a = vec3.sub(vec3.create(), allPoints[i], ipdCoord)
        const b = vec3.sub(vec3.create(), allPoints[(i + 1) % allPoints.length], ipdCoord)
        const angle = vec3.angle(a, b)
        sum += angle
Xiao Gui's avatar
Xiao Gui committed
        const dist = vec3.length(a)
        if (dist < minDist) {
          minDist = dist
          pos = allPoints[i]
        }
      }

      /**
       * Since inPlaneDisplacement is a point on the plane
       * If the sum of all points == PI, then the point is enclosed
       * Assuming simple concave shapes
       */
      const isEnclosed = Math.abs(sum - (2 * Math.PI)) < 0.05

      const resultant = isEnclosed
        ? vec3.add(vec3.create(), inPlaneDisplacement, pt0)
        : pos
      vec3.scale(resultant, resultant, 1e6)
      position = Array.from(resultant)
    }
Xiao Gui's avatar
Xiao Gui committed

    this.store.dispatch(
      atlasSelection.actions.navigateTo({
        navigation: {
          orientation: Array.from(q),
          position,
Xiao Gui's avatar
Xiao Gui committed
  toggleVisibility(): void{
Xiao Gui's avatar
Xiao Gui committed
    this.visible = !this.visible
    this.viewer.nehubaViewer.ngviewer.layerManager.getLayerByName(this.name).setVisible(this.visible)
  }
}