<commit_before>/**
 * @file Viewer
 * @author <NAME> <<EMAIL>>
 * @private
 */

import { Signal } from 'signals'
import {
  PerspectiveCamera, OrthographicCamera,
  Box3, Vector3, Matrix4, Color,
  WebGLRenderer, WebGLRenderTarget,
  NearestFilter, LinearFilter, AdditiveBlending,
  RGBAFormat, FloatType, HalfFloatType, UnsignedByteType,
  ShaderMaterial,
  PlaneGeometry, Geometry,
  Scene, Mesh, Group, Object3D, Uniform,
  Fog, SpotLight, AmbientLight,
  BufferGeometry, BufferAttribute,
  LineSegments
} from 'three'

import '../shader/BasicLine.vert'
import '../shader/BasicLine.frag'
import '../shader/Quad.vert'
import '../shader/Quad.frag'

import {
  Debug, Log, WebglErrorMessage,
  setExtensionFragDepth, SupportsReadPixelsFloat, setSupportsReadPixelsFloat
} from '../globals'
import { degToRad } from '../math/math-utils'
import Stats from './stats'
import { getShader } from '../shader/shader-utils'
import { JitterVectors } from './viewer-constants'
import {
  makeImage, ImageParameters, testTextureSupport,
  sortProjectedPosition, updateMaterialUniforms
} from './viewer-utils'

import Buffer from '../buffer/buffer'

const pixelBufferFloat = new Float32Array(4)
const pixelBufferUint = new Uint8Array(4)

const tmpMatrix = new Matrix4()

function onBeforeRender (this: Object3D, renderer: WebGLRenderer, scene: Scene, camera: PerspectiveCamera|OrthographicCamera, geometry: Geometry, material: ShaderMaterial/*, group */) {
  const u = material.uniforms
  const updateList = []

  if (u.objectId) {
    u.objectId.value = SupportsReadPixelsFloat ? this.id : this.id / 255
    updateList.push('objectId')
  }

  if (u.modelViewMatrixInverse || u.modelViewMatrixInverseTranspose ||
      u.modelViewProjectionMatrix || u.modelViewProjectionMatrixInverse
  ) {
    this.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, this.matrixWorld)
  }

  if (u.modelViewMatrixInverse) {
    u.modelViewMatrixInverse.value.getInverse(this.modelViewMatrix)
    updateList.push('modelViewMatrixInverse')
  }

  if (u.modelViewMatrixInverseTranspose) {
    if (u.modelViewMatrixInverse) {
      u.modelViewMatrixInverseTranspose.value.copy(
        u.modelViewMatrixInverse.value
      ).transpose()
    } else {
      u.modelViewMatrixInverseTranspose.value
        .getInverse(this.modelViewMatrix)
        .transpose()
    }
    updateList.push('modelViewMatrixInverseTranspose')
  }

  if (u.modelViewProjectionMatrix) {
    camera.updateProjectionMatrix()
    u.modelViewProjectionMatrix.value.multiplyMatrices(
      camera.projectionMatrix, this.modelViewMatrix
    )
    updateList.push('modelViewProjectionMatrix')
  }

  if (u.modelViewProjectionMatrixInverse) {
    if (u.modelViewProjectionMatrix) {
      tmpMatrix.copy(
        u.modelViewProjectionMatrix.value
      )
      u.modelViewProjectionMatrixInverse.value.getInverse(
        tmpMatrix
      )
    } else {
      camera.updateProjectionMatrix()
      tmpMatrix.multiplyMatrices(
        camera.projectionMatrix, this.modelViewMatrix
      )
      u.modelViewProjectionMatrixInverse.value.getInverse(
        tmpMatrix
      )
    }
    updateList.push('modelViewProjectionMatrixInverse')
  }

  if (updateList.length) {
    const materialProperties = renderer.properties.get(material)

    if (materialProperties.program) {
      const gl = renderer.getContext()
      const p = materialProperties.program
      gl.useProgram(p.program)
      const pu = p.getUniforms()

      updateList.forEach(function (name) {
        pu.setValue(gl, name, u[ name ].value)
      })
    }
  }
}

interface ViewerSignals {
  ticked: Signal
}

interface ViewerParameters {
  fogColor: Color
  fogNear: number
  fogFar: number

  backgroundColor: Color

  cameraType: 'perspective'|'orthographic'
  cameraFov: number
  cameraZ: number

  clipNear: number
  clipFar: number
  clipDist: number

  lightColor: Color
  lightIntensity: number
  ambientColor: Color
  ambientIntensity: number

  sampleLevel: number
}

interface BufferInstance {
  matrix: Matrix4
}

/**
 * Viewer class
 * @class
 * @param {String|Element} [idOrElement] - dom id or element
 */
export default class Viewer {
  signals: ViewerSignals

  container: HTMLElement

  private rendering: boolean
  private renderPending: boolean
  private lastRenderedPicking: boolean
  private isStill: boolean

  sampleLevel: number
  private cDist: number
  private bRadius: number

  private parameters: ViewerParameters
  stats: Stats

  perspectiveCamera: PerspectiveCamera
  private orthographicCamera: OrthographicCamera
  camera: PerspectiveCamera|OrthographicCamera

  width: number
  height: number

  scene: Scene
  private spotLight: SpotLight
  private ambientLight: AmbientLight
  rotationGroup: Group
  translationGroup: Group
  private modelGroup: Group
  private pickingGroup: Group
  private backgroundGroup: Group
  private helperGroup: Group

  renderer: WebGLRenderer
  private supportsHalfFloat: boolean

  private pickingTarget: WebGLRenderTarget
  private sampleTarget: WebGLRenderTarget
  private holdTarget: WebGLRenderTarget

  private compositeUniforms: {
    tForeground: Uniform
    scale: Uniform
  }
  private compositeMaterial: ShaderMaterial
  private compositeCamera: OrthographicCamera
  private compositeScene: Scene

  private boundingBoxMesh: LineSegments
  boundingBox = new Box3()
  private boundingBoxSize = new Vector3()
  private boundingBoxLength = 0

  private info = {
    memory: {
      programs: 0,
      geometries: 0,
      textures: 0
    },
    render: {
      calls: 0,
      vertices: 0,
      faces: 0,
      points: 0
    }
  }

  private distVector = new Vector3()

  constructor (idOrElement: string|HTMLElement) {
    this.signals = {
      ticked: new Signal()
    }

    if (typeof idOrElement === 'string') {
      const elm = document.getElementById(idOrElement)
      if (elm === null) {
        this.container = document.createElement('div')
      }else {
        this.container = elm
      }
    } else if (idOrElement instanceof HTMLElement) {
      this.container = idOrElement
    } else {
      this.container = document.createElement('div')
    }

    if (this.container === document.body) {
      this.width = window.innerWidth || 1
      this.height = window.innerHeight || 1
    } else {
      const box = this.container.getBoundingClientRect()
      this.width = box.width || 1
      this.height = box.height || 1
    }

    this._initParams()
    this._initStats()
    this._initCamera()
    this._initScene()

    if (this._initRenderer() === false) {
      Log.error('Viewer: could not initialize renderer')
      return
    }

    this._initHelper()

    // fog & background
    this.setBackground()
    this.setFog()

    this.animate = this.animate.bind(this)
  }

  private _initParams () {
    this.parameters = {
      fogColor: new Color(0x000000),
      fogNear: 50,
      fogFar: 100,

      backgroundColor: new Color(0x000000),

      cameraType: 'perspective',
      cameraFov: 40,
      cameraZ: -80, // FIXME initial value should be automatically determined

      clipNear: 0,
      clipFar: 100,
      clipDist: 10,

      lightColor: new Color(0xdddddd),
      lightIntensity: 1.0,
      ambientColor: new Color(0xdddddd),
      ambientIntensity: 0.2,

      sampleLevel: 0
    }
  }

  private _initCamera () {
    const lookAt = new Vector3(0, 0, 0)
    const {width, height} = this

    this.perspectiveCamera = new PerspectiveCamera(
      this.parameters.cameraFov, width / height
    )
    this.perspectiveCamera.position.z = this.parameters.cameraZ
    this.perspectiveCamera.lookAt(lookAt)

    this.orthographicCamera = new OrthographicCamera(
      width / -2, width / 2, height / 2, height / -2
    )
    this.orthographicCamera.position.z = this.parameters.cameraZ
    this.orthographicCamera.lookAt(lookAt)

    if (this.parameters.cameraType === 'orthographic') {
      this.camera = this.orthographicCamera
    } else {  // parameters.cameraType === "perspective"
      this.camera = this.perspectiveCamera
    }
    this.camera.updateProjectionMatrix()
  }

  private _initStats () {
    this.stats = new Stats()
  }

  private _initScene () {
    if (!this.scene) {
      this.scene = new Scene()
      this.scene.name = 'scene'
    }

    this.rotationGroup = new Group()
    this.rotationGroup.name = 'rotationGroup'
    this.scene.add(this.rotationGroup)

    this.translationGroup = new Group()
    this.translationGroup.name = 'translationGroup'
    this.rotationGroup.add(this.translationGroup)

    this.modelGroup = new Group()
    this.modelGroup.name = 'modelGroup'
    this.translationGroup.add(this.modelGroup)

    this.pickingGroup = new Group()
    this.pickingGroup.name = 'pickingGroup'
    this.translationGroup.add(this.pickingGroup)

    this.backgroundGroup = new Group()
    this.backgroundGroup.name = 'backgroundGroup'
    this.translationGroup.add(this.backgroundGroup)

    this.helperGroup = new Group()
    this.helperGroup.name = 'helperGroup'
    this.translationGroup.add(this.helperGroup)

    // fog

    this.scene.fog = new Fog(this.parameters.fogColor.getHex())

    // light

    this.spotLight = new SpotLight(
      this.parameters.lightColor.getHex(), this.parameters.lightIntensity
    )
    this.scene.add(this.spotLight)

    this.ambientLight = new AmbientLight(
      this.parameters.ambientColor.getHex(), this.parameters.ambientIntensity
    )
    this.scene.add(this.ambientLight)
  }

  private _initRenderer () {
    const dpr = window.devicePixelRatio
    const {width, height} = this

    try {
      this.renderer = new WebGLRenderer({
        preserveDrawingBuffer: true,
        alpha: true,
        antialias: true
      })
    } catch (e) {
      this.container.innerHTML = WebglErrorMessage
      return false
    }
    this.renderer.setPixelRatio(dpr)
    this.renderer.setSize(width, height)
    this.renderer.autoClear = false
    this.renderer.sortObjects = true

    const gl = this.renderer.getContext()
    // console.log(gl.getContextAttributes().antialias)
    // console.log(gl.getParameter(gl.SAMPLES))

    setExtensionFragDepth(this.renderer.extensions.get('EXT_frag_depth'))
    this.renderer.extensions.get('OES_element_index_uint')

    setSupportsReadPixelsFloat(
      (this.renderer.extensions.get('OES_texture_float') &&
        this.renderer.extensions.get('WEBGL_color_buffer_float')) ||
      (this.renderer.extensions.get('OES_texture_float') &&
        testTextureSupport(gl, gl.FLOAT))
    )

    this.container.appendChild(this.renderer.domElement)

    const dprWidth = width * dpr
    const dprHeight = height * dpr

    // picking texture

    this.renderer.extensions.get('OES_texture_float')
    this.supportsHalfFloat = (
      this.renderer.extensions.get('OES_texture_half_float') &&
      testTextureSupport(gl, 0x8D61)<commit_msg>workaround for readPixel not working with float type on Safari<commit_after>/**
 * @file Viewer
 * @author <NAME> <<EMAIL>>
 * @private
 */

import { Signal } from 'signals'
import {
  PerspectiveCamera, OrthographicCamera,
  Box3, Vector3, Matrix4, Color,
  WebGLRenderer, WebGLRenderTarget,
  NearestFilter, LinearFilter, AdditiveBlending,
  RGBAFormat, FloatType, HalfFloatType, UnsignedByteType,
  ShaderMaterial,
  PlaneGeometry, Geometry,
  Scene, Mesh, Group, Object3D, Uniform,
  Fog, SpotLight, AmbientLight,
  BufferGeometry, BufferAttribute,
  LineSegments
} from 'three'

import '../shader/BasicLine.vert'
import '../shader/BasicLine.frag'
import '../shader/Quad.vert'
import '../shader/Quad.frag'

import {
  Debug, Log, WebglErrorMessage, Browser,
  setExtensionFragDepth, SupportsReadPixelsFloat, setSupportsReadPixelsFloat
} from '../globals'
import { degToRad } from '../math/math-utils'
import Stats from './stats'
import { getShader } from '../shader/shader-utils'
import { JitterVectors } from './viewer-constants'
import {
  makeImage, ImageParameters, testTextureSupport,
  sortProjectedPosition, updateMaterialUniforms
} from './viewer-utils'

import Buffer from '../buffer/buffer'

const pixelBufferFloat = new Float32Array(4)
const pixelBufferUint = new Uint8Array(4)

const tmpMatrix = new Matrix4()

function onBeforeRender (this: Object3D, renderer: WebGLRenderer, scene: Scene, camera: PerspectiveCamera|OrthographicCamera, geometry: Geometry, material: ShaderMaterial/*, group */) {
  const u = material.uniforms
  const updateList = []

  if (u.objectId) {
    u.objectId.value = SupportsReadPixelsFloat ? this.id : this.id / 255
    updateList.push('objectId')
  }

  if (u.modelViewMatrixInverse || u.modelViewMatrixInverseTranspose ||
      u.modelViewProjectionMatrix || u.modelViewProjectionMatrixInverse
  ) {
    this.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, this.matrixWorld)
  }

  if (u.modelViewMatrixInverse) {
    u.modelViewMatrixInverse.value.getInverse(this.modelViewMatrix)
    updateList.push('modelViewMatrixInverse')
  }

  if (u.modelViewMatrixInverseTranspose) {
    if (u.modelViewMatrixInverse) {
      u.modelViewMatrixInverseTranspose.value.copy(
        u.modelViewMatrixInverse.value
      ).transpose()
    } else {
      u.modelViewMatrixInverseTranspose.value
        .getInverse(this.modelViewMatrix)
        .transpose()
    }
    updateList.push('modelViewMatrixInverseTranspose')
  }

  if (u.modelViewProjectionMatrix) {
    camera.updateProjectionMatrix()
    u.modelViewProjectionMatrix.value.multiplyMatrices(
      camera.projectionMatrix, this.modelViewMatrix
    )
    updateList.push('modelViewProjectionMatrix')
  }

  if (u.modelViewProjectionMatrixInverse) {
    if (u.modelViewProjectionMatrix) {
      tmpMatrix.copy(
        u.modelViewProjectionMatrix.value
      )
      u.modelViewProjectionMatrixInverse.value.getInverse(
        tmpMatrix
      )
    } else {
      camera.updateProjectionMatrix()
      tmpMatrix.multiplyMatrices(
        camera.projectionMatrix, this.modelViewMatrix
      )
      u.modelViewProjectionMatrixInverse.value.getInverse(
        tmpMatrix
      )
    }
    updateList.push('modelViewProjectionMatrixInverse')
  }

  if (updateList.length) {
    const materialProperties = renderer.properties.get(material)

    if (materialProperties.program) {
      const gl = renderer.getContext()
      const p = materialProperties.program
      gl.useProgram(p.program)
      const pu = p.getUniforms()

      updateList.forEach(function (name) {
        pu.setValue(gl, name, u[ name ].value)
      })
    }
  }
}

interface ViewerSignals {
  ticked: Signal
}

interface ViewerParameters {
  fogColor: Color
  fogNear: number
  fogFar: number

  backgroundColor: Color

  cameraType: 'perspective'|'orthographic'
  cameraFov: number
  cameraZ: number

  clipNear: number
  clipFar: number
  clipDist: number

  lightColor: Color
  lightIntensity: number
  ambientColor: Color
  ambientIntensity: number

  sampleLevel: number
}

interface BufferInstance {
  matrix: Matrix4
}

/**
 * Viewer class
 * @class
 * @param {String|Element} [idOrElement] - dom id or element
 */
export default class Viewer {
  signals: ViewerSignals

  container: HTMLElement

  private rendering: boolean
  private renderPending: boolean
  private lastRenderedPicking: boolean
  private isStill: boolean

  sampleLevel: number
  private cDist: number
  private bRadius: number

  private parameters: ViewerParameters
  stats: Stats

  perspectiveCamera: PerspectiveCamera
  private orthographicCamera: OrthographicCamera
  camera: PerspectiveCamera|OrthographicCamera

  width: number
  height: number

  scene: Scene
  private spotLight: SpotLight
  private ambientLight: AmbientLight
  rotationGroup: Group
  translationGroup: Group
  private modelGroup: Group
  private pickingGroup: Group
  private backgroundGroup: Group
  private helperGroup: Group

  renderer: WebGLRenderer
  private supportsHalfFloat: boolean

  private pickingTarget: WebGLRenderTarget
  private sampleTarget: WebGLRenderTarget
  private holdTarget: WebGLRenderTarget

  private compositeUniforms: {
    tForeground: Uniform
    scale: Uniform
  }
  private compositeMaterial: ShaderMaterial
  private compositeCamera: OrthographicCamera
  private compositeScene: Scene

  private boundingBoxMesh: LineSegments
  boundingBox = new Box3()
  private boundingBoxSize = new Vector3()
  private boundingBoxLength = 0

  private info = {
    memory: {
      programs: 0,
      geometries: 0,
      textures: 0
    },
    render: {
      calls: 0,
      vertices: 0,
      faces: 0,
      points: 0
    }
  }

  private distVector = new Vector3()

  constructor (idOrElement: string|HTMLElement) {
    this.signals = {
      ticked: new Signal()
    }

    if (typeof idOrElement === 'string') {
      const elm = document.getElementById(idOrElement)
      if (elm === null) {
        this.container = document.createElement('div')
      }else {
        this.container = elm
      }
    } else if (idOrElement instanceof HTMLElement) {
      this.container = idOrElement
    } else {
      this.container = document.createElement('div')
    }

    if (this.container === document.body) {
      this.width = window.innerWidth || 1
      this.height = window.innerHeight || 1
    } else {
      const box = this.container.getBoundingClientRect()
      this.width = box.width || 1
      this.height = box.height || 1
    }

    this._initParams()
    this._initStats()
    this._initCamera()
    this._initScene()

    if (this._initRenderer() === false) {
      Log.error('Viewer: could not initialize renderer')
      return
    }

    this._initHelper()

    // fog & background
    this.setBackground()
    this.setFog()

    this.animate = this.animate.bind(this)
  }

  private _initParams () {
    this.parameters = {
      fogColor: new Color(0x000000),
      fogNear: 50,
      fogFar: 100,

      backgroundColor: new Color(0x000000),

      cameraType: 'perspective',
      cameraFov: 40,
      cameraZ: -80, // FIXME initial value should be automatically determined

      clipNear: 0,
      clipFar: 100,
      clipDist: 10,

      lightColor: new Color(0xdddddd),
      lightIntensity: 1.0,
      ambientColor: new Color(0xdddddd),
      ambientIntensity: 0.2,

      sampleLevel: 0
    }
  }

  private _initCamera () {
    const lookAt = new Vector3(0, 0, 0)
    const {width, height} = this

    this.perspectiveCamera = new PerspectiveCamera(
      this.parameters.cameraFov, width / height
    )
    this.perspectiveCamera.position.z = this.parameters.cameraZ
    this.perspectiveCamera.lookAt(lookAt)

    this.orthographicCamera = new OrthographicCamera(
      width / -2, width / 2, height / 2, height / -2
    )
    this.orthographicCamera.position.z = this.parameters.cameraZ
    this.orthographicCamera.lookAt(lookAt)

    if (this.parameters.cameraType === 'orthographic') {
      this.camera = this.orthographicCamera
    } else {  // parameters.cameraType === "perspective"
      this.camera = this.perspectiveCamera
    }
    this.camera.updateProjectionMatrix()
  }

  private _initStats () {
    this.stats = new Stats()
  }

  private _initScene () {
    if (!this.scene) {
      this.scene = new Scene()
      this.scene.name = 'scene'
    }

    this.rotationGroup = new Group()
    this.rotationGroup.name = 'rotationGroup'
    this.scene.add(this.rotationGroup)

    this.translationGroup = new Group()
    this.translationGroup.name = 'translationGroup'
    this.rotationGroup.add(this.translationGroup)

    this.modelGroup = new Group()
    this.modelGroup.name = 'modelGroup'
    this.translationGroup.add(this.modelGroup)

    this.pickingGroup = new Group()
    this.pickingGroup.name = 'pickingGroup'
    this.translationGroup.add(this.pickingGroup)

    this.backgroundGroup = new Group()
    this.backgroundGroup.name = 'backgroundGroup'
    this.translationGroup.add(this.backgroundGroup)

    this.helperGroup = new Group()
    this.helperGroup.name = 'helperGroup'
    this.translationGroup.add(this.helperGroup)

    // fog

    this.scene.fog = new Fog(this.parameters.fogColor.getHex())

    // light

    this.spotLight = new SpotLight(
      this.parameters.lightColor.getHex(), this.parameters.lightIntensity
    )
    this.scene.add(this.spotLight)

    this.ambientLight = new AmbientLight(
      this.parameters.ambientColor.getHex(), this.parameters.ambientIntensity
    )
    this.scene.add(this.ambientLight)
  }

  private _initRenderer () {
    const dpr = window.devicePixelRatio
    const {width, height} = this

    try {
      this.renderer = new WebGLRenderer({
        preserveDrawingBuffer: true,
        alpha: true,
        antialias: true
      })
    } catch (e) {
      this.container.innerHTML = WebglErrorMessage
      return false
    }
    this.renderer.setPixelRatio(dpr)
    this.renderer.setSize(width, height)
    this.renderer.autoClear = false
    this.renderer.sortObjects = true

    const gl = this.renderer.getContext()
    // console.log(gl.getContextAttributes().antialias)
    // console.log(gl.getParameter(gl.SAMPLES))

    setExtensionFragDepth(this.renderer.extensions.get('EXT_frag_depth'))
    this.renderer.extensions.get('OES_element_index_uint')

    setSupportsReadPixelsFloat(
      Browser !== 'Safari' && (
        (this.renderer.extensions.get('OES_texture_float') &&
          this.renderer.extensions.get('WEBGL_color_buffer_float')) ||
        (this.renderer.extensions.get('OES_texture_float') &&
          testTextureSupport(gl, gl.FLOAT))
      )
    )

    this.container.appendChild(this.renderer.domElement)

    const dprWidth = width * dpr
    const dprHeight = height * dpr

    // picking texture

    this.renderer.extensions.get('OES_texture_float')
    this.supportsHalfFloat = (
      this.renderer.extensions.get('OES_texture_half_float') &&
      testTextureSupport(gl, 0x8D61)
{'fraction_non_alphanumeric': 0.09956035904011724, 'fraction_numerical': 0.007144165598094889, 'mean_word_length': 3.309650680876258, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4292814', 'n_tokens_mistral': 6797, 'n_tokens_neox': 6572, 'n_words': 1349}