显示优化

This commit is contained in:
tianmo
2026-09-05 22:00:03 +08:00
parent 0a0ab592a4
commit 26371ac7b4
6 changed files with 153 additions and 98 deletions
+76 -21
View File
@@ -438,12 +438,14 @@ function drawAxis() {
if (v === 0) continue
g.lineStyle(tickW, axisColor, 0.25)
g.moveTo(v, cy - 4); g.lineTo(v, cy + 4)
g.moveTo(-v, cy - 4); g.lineTo(-v, cy + 4)
}
for (let y = 0; y <= halfH; y += step) {
const v = Math.round(y / step) * step
if (v === 0) continue
g.lineStyle(tickW, axisColor, 0.25)
g.moveTo(cx - 4, y); g.lineTo(cx + 4, y)
g.moveTo(cx - 4, -y); g.lineTo(cx + 4, -y)
}
}
// 数值标签(文字放 axisLabelLayer,不随 world 缩放,尺寸/清晰度恒定)
@@ -1177,16 +1179,68 @@ function drawGizmo() {
// 粒子→骨骼映射:每个活动粒子 = 一根挂在 root 下的骨骼
type BonePreview = { boneName: string; x: number; y: number; rotation: number; parentX?: number; parentY?: number; trail?: boolean }
function appendStateBones(target: BonePreview[], state: ParticleState) {
// 粒子 rotation 使用数学角度,而 Pixi 画布 Y 轴向下,因此显示轴取反。
target.push({ boneName: state.boneName, x: state.x, y: state.y, rotation: -state.rotation })
function appendStateBones(target: BonePreview[], state: ParticleState, parentX: number, parentY: number) {
// 粒子快照同时保存局部坐标和烘焙世界坐标。骨骼调试图层挂在 world 下,必须使用
// 世界坐标,才能与发生位移、旋转、缩放或跟随后的粒子保持逐点重合。
const hasWorldPosition = Number.isFinite(state.worldX) && Number.isFinite(state.worldY)
const hasWorldRotation = Number.isFinite(state.worldRotation)
target.push({
boneName: state.boneName,
x: hasWorldPosition ? state.worldX! : state.x,
y: hasWorldPosition ? -state.worldY! : state.y,
rotation: hasWorldRotation ? -state.worldRotation! : -state.rotation,
parentX,
parentY,
})
if (!state.trailState?.bones.length) return
state.trailState.bones.forEach((bone) => {
// 导出结构中拖尾骨骼全部直接挂在粒子系统骨骼下,画布连线也保持同级语义。
target.push({ boneName: bone.boneName, x: bone.x, y: bone.y, rotation: bone.rotation, parentX: 0, parentY: 0, trail: true })
const hasBoneWorldPosition = Number.isFinite(bone.worldX) && Number.isFinite(bone.worldY)
const hasBoneWorldRotation = Number.isFinite(bone.worldRotation)
target.push({
boneName: bone.boneName,
x: hasBoneWorldPosition ? bone.worldX! : bone.x,
y: hasBoneWorldPosition ? -bone.worldY! : bone.y,
rotation: hasBoneWorldRotation ? -bone.worldRotation! : bone.rotation,
parentX,
parentY,
trail: true,
})
})
}
function drawDirectionalAxis(
g: Graphics,
x: number,
y: number,
directionX: number,
directionY: number,
length: number,
width: number,
color: number,
) {
const negativeLength = length * 0.62
const tipX = x + directionX * length
const tipY = y + directionY * length
const headLength = Math.max(2.5, length * 0.32)
const headWidth = Math.max(1.7, length * 0.22)
const baseX = tipX - directionX * headLength
const baseY = tipY - directionY * headLength
const perpendicularX = -directionY
const perpendicularY = directionX
g.lineStyle(width, color, 0.95)
g.moveTo(x - directionX * negativeLength, y - directionY * negativeLength)
g.lineTo(baseX, baseY)
g.lineStyle(0)
g.beginFill(color, 0.95)
g.drawPolygon([
tipX, tipY,
baseX + perpendicularX * headWidth, baseY + perpendicularY * headWidth,
baseX - perpendicularX * headWidth, baseY - perpendicularY * headWidth,
])
g.endFill()
}
function drawBones(bones: BonePreview[]) {
if (!boneGfx) return
const g = boneGfx
@@ -1199,27 +1253,18 @@ function drawBones(bones: BonePreview[]) {
// 所有尺寸先定义为屏幕像素,再除以画布缩放,保证缩放画布时视觉大小不变。
const pointRadius = 2.5 * pointSize / viewScale
const forwardLength = 4.5 * axisSize / viewScale
const sideLength = 2.5 * axisSize / viewScale
const forwardWidth = Math.max(0.65, axisSize * 0.625) / viewScale
const sideWidth = Math.max(0.55, axisSize * 0.5) / viewScale
// root → 每颗粒子骨骼 的连线
for (const b of bones) {
const color = b.trail ? 0xff4d8d : 0xff8c5a
g.lineStyle(1, color, b.trail ? 0.65 : 0.35)
g.moveTo(b.parentX ?? cx, b.parentY ?? cy)
g.lineTo(b.x, b.y)
// 小型局部方向轴:青色为骨骼正方向,黄色为垂直方向
// 局部坐标轴为两根交叉轴线,仅在 +X、+Y 端绘制箭头
const angle = b.rotation * Math.PI / 180
const cos = Math.cos(angle), sin = Math.sin(angle)
g.lineStyle(forwardWidth, 0x38d9ff, 0.95)
g.moveTo(b.x, b.y)
g.lineTo(b.x + cos * forwardLength, b.y + sin * forwardLength)
g.beginFill(0x38d9ff, 0.95)
g.drawCircle(b.x + cos * forwardLength, b.y + sin * forwardLength, Math.max(0.8, axisSize * 0.625) / viewScale)
g.endFill()
g.lineStyle(sideWidth, 0xffd166, 0.85)
g.moveTo(b.x, b.y)
g.lineTo(b.x - sin * sideLength, b.y + cos * sideLength)
drawDirectionalAxis(g, b.x, b.y, cos, sin, forwardLength, forwardWidth, 0xef5350)
drawDirectionalAxis(g, b.x, b.y, sin, -cos, forwardLength, forwardWidth, 0x55c96b)
// 骨骼点
g.beginFill(color, 0.9)
g.drawCircle(b.x, b.y, pointRadius)
@@ -1523,6 +1568,13 @@ function loop() {
}
let total = 0
const collected: BonePreview[] = []
const selectedBoneSystemId = store.settings.showBones && store.activeObjectType === 'particle'
? store.activeObjectId
: 0
const collectSelectedBones = (system: { id: number; visible: boolean } | undefined, emitter: ParticleEmitter, state: ParticleState) => {
if (!system || system.id !== selectedBoneSystemId || system.visible === false) return
appendStateBones(collected, state, emitter.position.x, emitter.position.y)
}
const tl = store.timeline
const fps = tl.fps
const frameDuration = 1 / Math.max(1, fps)
@@ -1567,8 +1619,9 @@ function loop() {
const states = forceEmptyInitialFrame ? [] : (loopFrame || em.update(frameDuration))
if (forceEmptyInitialFrame) em.apply([])
else if (loopFrame) em.apply(loopFrame)
const systemVisible = system?.visible !== false
if (systemVisible) for (const state of states) if (state.active) appendStateBones(collected, state)
if (system?.visible !== false) {
for (const state of states) if (state.active) collectSelectedBones(system, em, state)
}
total += states.length
}
for (const sys of store.systems) {
@@ -1590,7 +1643,7 @@ function loop() {
if (emitter && frame) {
emitter.apply(frame)
for (const state of frame) if (state.active) {
if (sys.visible !== false) appendStateBones(collected, state)
collectSelectedBones(sys, emitter, state)
total++
}
}
@@ -1627,14 +1680,16 @@ function loop() {
if ((tl.recorded || hasPreparedLoopFrames) && emitter && frame) {
emitter.apply(frame)
for (const state of frame) if (state.active) {
if (sys.visible !== false) appendStateBones(collected, state)
collectSelectedBones(sys, emitter, state)
total++
}
} else if (emitter) {
// 尚未录制时,第 0 帧也必须保持绝对空白,不能由 update(0) 提前触发发射。
const states = tl.frame === 0 ? [] : emitter.update(0)
if (tl.frame === 0) emitter.apply([])
if (sys.visible !== false) for (const state of states) if (state.active) appendStateBones(collected, state)
if (sys.visible !== false) {
for (const state of states) if (state.active) collectSelectedBones(sys, emitter, state)
}
total += emitter.activeCount
}
}