threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
341 lines (232 loc) • 9.04 kB
text/typescript
import {
Camera,
Color,
InterleavedBufferAttribute,
Object3D,
OrthographicCamera,
PerspectiveCamera,
Vector3,
} from 'three'
import {ACameraHelperWidget} from './ACameraHelperWidget'
import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js'
import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js'
import {LineMaterial2} from '../../core'
import {uiToggle} from 'uiconfig.js'
import {onChange2} from 'ts-browser-helpers'
/**
* Fork of CameraHelper from three.js
* - shows frustum, line of sight and up of the camera
* - suitable for fast updates
* - based on frustum visualization in lightgl.js shadowmap example
* https://github.com/evanw/lightgl.js/blob/master/tests/shadowmap.html
*/
export class CameraHelper2 extends ACameraHelperWidget {
protected _vector = new Vector3()
protected _camera = new Camera()
line: LineSegments2
pointMap: Record<string, number[]>
// Store original colors for reapplication
private _colorFrustum = new Color(0xffaa00)
private _colorCone = new Color(0xff0000)
private _colorUp = new Color(0x00aaff)
private _colorTarget = new Color(0xffffff)
private _colorCross = new Color(0x333333)
showFar = false
constructor(camera: PerspectiveCamera|OrthographicCamera) {
super(camera)
const geometry = new LineSegmentsGeometry()
const material = new LineMaterial2({
color: 0xffffff,
linewidth: 3, // in world units with size attenuation, pixels otherwise
vertexColors: true,
worldUnits: false,
dashed: false,
alphaToCoverage: true,
toneMapped: false,
transparent: true,
depthTest: true,
depthWrite: false,
allowOverride: false,
})
material.userData.renderToGBuffer = false
material.userData.renderToDepth = false
const {vertices, colors, pointMap} = generateVertices()
geometry.setPositions(vertices)
geometry.setColors(colors)
this.line = new LineSegments2(geometry, material)
this.line.frustumCulled = false
this.add(this.line)
this.pointMap = pointMap
this.update()
// colors
this.setColors(this._colorFrustum, this._colorCone, this._colorUp, this._colorTarget, this._colorCross)
}
setColors(frustum: Color, cone: Color, up: Color, target: Color, cross: Color) {
const geometry = this.line.geometry
const colorAttribute = geometry.getAttribute('instanceColorStart')
const colorAttribute2 = geometry.getAttribute('instanceColorEnd')
let i = 0
function setXYZ(color: Color) {
colorAttribute.setXYZ(i, color.r, color.g, color.b)
colorAttribute2.setXYZ(i, color.r, color.g, color.b)
i++
}
// up
setXYZ(up) // u1, u2
setXYZ(up) // u2, u3
setXYZ(up) // u3, u1
// cross
setXYZ(cross) // cn1, cn2
setXYZ(cross) // cn3, cn4
setXYZ(cross) // cf1, cf2
setXYZ(cross) // cf3, cf4
// near
setXYZ(frustum) // n1, n2
setXYZ(frustum) // n2, n4
setXYZ(frustum) // n4, n3
setXYZ(frustum) // n3, n1
// cone
setXYZ(cone) // p, n1
setXYZ(cone) // p, n2
setXYZ(cone) // p, n3
setXYZ(cone) // p, n4
// far
setXYZ(frustum) // f1, f2
setXYZ(frustum) // f2, f4
setXYZ(frustum) // f4, f3
setXYZ(frustum) // f3, f1
// sides
setXYZ(frustum) // n1, f1
setXYZ(frustum) // n2, f2
setXYZ(frustum) // n3, f3
setXYZ(frustum) // n4, f4
// target
setXYZ(target) // c, t
setXYZ(cross) // p, c
colorAttribute.needsUpdate = true
colorAttribute2.needsUpdate = true
}
update() {
if (!this.camera || !this.line) return
const geometry = this.line.geometry
const pointMap = this.pointMap
const w = 1, h = 1
// we need just camera projection matrix inverse
// world matrix must be identity
// eslint-disable-next-line @typescript-eslint/naming-convention
const {_camera, _vector} = this
;(this.camera as PerspectiveCamera).updateProjectionMatrix()
_camera.projectionMatrixInverse.copy(this.camera.projectionMatrixInverse)
// center / target
setPoint('c', pointMap, geometry, _camera, 0, 0, -1, _vector)
setPoint('t', pointMap, geometry, _camera, 0, 0, 1, _vector)
// near
setPoint('n1', pointMap, geometry, _camera, -w, -h, -1, _vector)
setPoint('n2', pointMap, geometry, _camera, w, -h, -1, _vector)
setPoint('n3', pointMap, geometry, _camera, -w, h, -1, _vector)
setPoint('n4', pointMap, geometry, _camera, w, h, -1, _vector)
// far
setPoint('f1', pointMap, geometry, _camera, -w, -h, 1, _vector)
setPoint('f2', pointMap, geometry, _camera, w, -h, 1, _vector)
setPoint('f3', pointMap, geometry, _camera, -w, h, 1, _vector)
setPoint('f4', pointMap, geometry, _camera, w, h, 1, _vector)
// up
setPoint('u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, -1, _vector)
setPoint('u2', pointMap, geometry, _camera, -w * 0.7, h * 1.1, -1, _vector)
setPoint('u3', pointMap, geometry, _camera, 0, h * 2, -1, _vector)
// cross
setPoint('cf1', pointMap, geometry, _camera, -w, 0, 1, _vector)
setPoint('cf2', pointMap, geometry, _camera, w, 0, 1, _vector)
setPoint('cf3', pointMap, geometry, _camera, 0, -h, 1, _vector)
setPoint('cf4', pointMap, geometry, _camera, 0, h, 1, _vector)
setPoint('cn1', pointMap, geometry, _camera, -w, 0, -1, _vector)
setPoint('cn2', pointMap, geometry, _camera, w, 0, -1, _vector)
setPoint('cn3', pointMap, geometry, _camera, 0, -h, -1, _vector)
setPoint('cn4', pointMap, geometry, _camera, 0, h, -1, _vector)
geometry.getAttribute('instanceStart').needsUpdate = true
geometry.getAttribute('instanceEnd').needsUpdate = true
geometry.computeBoundingBox()
geometry.computeBoundingSphere()
// Update colors to reflect showFar state
this.setColors(this._colorFrustum, this._colorCone, this._colorUp, this._colorTarget, this._colorCross)
this.line.geometry.instanceCount = this.showFar ? Infinity : 15
super.update()
}
dispose() {
this.line.geometry.dispose()
this.line.material.dispose()
super.dispose()
}
static Check(camera: Object3D) {
return (camera as any).isCamera
}
static Create(camera: Object3D) {
return new CameraHelper2(camera as any)
}
}
function setPoint(point: string, pointMap: Record<string, number[]>, geometry: LineSegmentsGeometry, camera: Camera, x: number, y: number, z: number, _vector: Vector3) {
_vector.set(x, y, z).unproject(camera)
const points = pointMap[ point ]
if (points !== undefined) {
const position1 = geometry.getAttribute('instanceStart') as InterleavedBufferAttribute
const position2 = geometry.getAttribute('instanceEnd') as InterleavedBufferAttribute
for (let i = 0, l = points.length; i < l; i++) {
const j = Math.floor(points[ i ] / 2.)
;(points[ i ] % 2 === 0 ? position1 : position2).setXYZ(j, _vector.x, _vector.y, _vector.z)
// (i % 2 === 0 ? position1 : position2).setXYZ(points[ i ], _vector.x, _vector.y, _vector.z)
}
}
}
function generateVertices() {
const vertices: number[] = []
const colors: number[] = []
const pointMap: any = {}
// up - 0
addLine('u1', 'u2')
addLine('u2', 'u3')
addLine('u3', 'u1')
// cross - 3
addLine('cn1', 'cn2')
addLine('cn3', 'cn4')
addLine('cf1', 'cf2')
addLine('cf3', 'cf4')
// frustum at the end
// near - 7
addLine('n1', 'n2')
addLine('n2', 'n4')
addLine('n4', 'n3')
addLine('n3', 'n1')
// cone - 11
addLine('p', 'n1')
addLine('p', 'n2')
addLine('p', 'n3')
addLine('p', 'n4')
// far - 15
addLine('f1', 'f2')
addLine('f2', 'f4')
addLine('f4', 'f3')
addLine('f3', 'f1')
// sides - 19
addLine('n1', 'f1')
addLine('n2', 'f2')
addLine('n3', 'f3')
addLine('n4', 'f4')
// target - 21
addLine('c', 't')
addLine('p', 'c')
function addLine(a: string, b: string) {
addPoint(a)
addPoint(b)
}
function addPoint(id: string) {
vertices.push(0, 0, 0)
colors.push(0, 0, 0)
if (pointMap[id] === undefined) {
pointMap[id] = []
}
pointMap[id].push(vertices.length / 3 - 1)
}
return {vertices, colors, pointMap}
}