UNPKG

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
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) @uiToggle() @onChange2(CameraHelper2.prototype.update) 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} }