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
text/typescript
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()
hMaterial: LineMaterial2
lineWidth = 5
color1: Color = new Color(0, 0, 1)
color2: Color = new Color(0, 1, 0)
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
}