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.

179 lines (142 loc) 5.77 kB
import {Bone, Color, ColorRepresentation, Matrix4, Object3D, Vector3} from 'three' import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' import {onChange2} from 'ts-browser-helpers' import {AHelperWidget} from './AHelperWidget' import {IUiConfigContainer, uiColor, uiSlider, uiToggle} from 'uiconfig.js' import {LineMaterial2} from '../../core' export class SkeletonHelper2 extends AHelperWidget { lineSegments: LineSegments2 bones: Bone[] declare object: (Object3D & IUiConfigContainer) | undefined private _vector = new Vector3() private _boneMatrix = new Matrix4() private _matrixWorldInv = new Matrix4() @onChange2(SkeletonHelper2.prototype.update) hMaterial: LineMaterial2 @onChange2(SkeletonHelper2.prototype.update) @uiSlider(undefined, [0.1, 20], 0.01) lineWidth = 5 @onChange2(SkeletonHelper2.prototype.update) @uiColor() color1: Color = new Color(0, 0, 1) @onChange2(SkeletonHelper2.prototype.update) @uiColor() color2: Color = new Color(0, 1, 0) @uiToggle() @onChange2(SkeletonHelper2.prototype.update) autoUpdate = true constructor(object: Object3D, color1?: ColorRepresentation, color2?: ColorRepresentation) { super(object) if (color1) this.color1.set(color1) if (color2) this.color2.set(color2) this.bones = getBoneList(object) // Create geometry and hMaterial const geometry = new LineSegmentsGeometry() this.hMaterial = new LineMaterial2({ vertexColors: true, linewidth: this.lineWidth, // resolution: new Vector2(1024, 1024), // Required for Line2 rendering worldUnits: false, dashed: false, alphaToCoverage: true, toneMapped: false, transparent: true, depthTest: true, depthWrite: false, allowOverride: false, }) this.hMaterial.userData.renderToGBuffer = false this.hMaterial.userData.renderToDepth = false this.lineSegments = new LineSegments2(geometry, this.hMaterial) this.lineSegments.frustumCulled = false this.add(this.lineSegments) this.matrix = object.matrixWorld this.matrixAutoUpdate = false this.update() } updateMatrixWorld(force?: boolean) { if (this.object && this.visible) this.autoUpdate && this.update(false) super.updateMatrixWorld(force) } update(setDirty = true) { if (!this.lineSegments || !this.object) return // Update hMaterial properties this.hMaterial.linewidth = this.lineWidth // Update colors in geometry const geometry = this.lineSegments.geometry const vertices: number[] = [] const colors: number[] = [] for (const bone of this.bones) { if (bone.parent && (bone.parent as Bone).isBone) { vertices.push(0, 0, 0) vertices.push(0, 0, 0) colors.push(this.color1.r, this.color1.g, this.color1.b) colors.push(this.color2.r, this.color2.g, this.color2.b) } } this._matrixWorldInv.copy(this.object.matrixWorld).invert() let j = 0 for (const bone of this.bones) { if (bone.parent && (bone.parent as any).isBone) { // Update parent position this._boneMatrix.multiplyMatrices(this._matrixWorldInv, bone.parent.matrixWorld) this._vector.setFromMatrixPosition(this._boneMatrix) // position.setXYZ(j, this._vector.x, this._vector.y, this._vector.z) vertices[3 * j] = this._vector.x vertices[3 * j + 1] = this._vector.y vertices[3 * j + 2] = this._vector.z // Update bone position this._boneMatrix.multiplyMatrices(this._matrixWorldInv, bone.matrixWorld) this._vector.setFromMatrixPosition(this._boneMatrix) // position.setXYZ(j + 1, this._vector.x, this._vector.y, this._vector.z) vertices[3 * j + 3] = this._vector.x vertices[3 * j + 4] = this._vector.y vertices[3 * j + 5] = this._vector.z j += 2 } } if (vertices.length > 0) { geometry.setPositions(vertices) geometry.setColors(colors) } else { // Fallback: create a simple test line if no bones found geometry.setPositions([0, 0, 0, 1, 0, 0]) geometry.setColors([1, 0, 0, 0, 1, 0]) } super.update(setDirty) } dispose() { this.lineSegments.geometry.dispose() this.lineSegments.material.dispose() super.dispose() } static Check(object: Object3D): boolean { let parentHas = false object.traverseAncestors(o=>{ if ((o as any)._hasSkeletonHelper) { parentHas = true } }) if (parentHas) return false return getBoneList(object).length > 0 } static Create(object: Object3D): SkeletonHelper2 { const helper = new SkeletonHelper2(object) ;(object as any)._hasSkeletonHelper = true return helper } } /** * Recursively collect all bones from an object hierarchy */ function getBoneList(object: Object3D): Bone[] { const boneList: Bone[] = [] if ((object as any).isBone === true) { boneList.push(object as Bone) } for (const child of object.children) { boneList.push(...getBoneList(child)) } return boneList }