显示优化

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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -8,8 +8,8 @@
html, body { margin: 0; padding: 0; background: #111; color: #eee; font-family: system-ui, -apple-system, sans-serif; }
#app { display: flex; flex-direction: column; height: 100vh; }
</style>
<script type="module" crossorigin src="/assets/index-BgZ-wFxF.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CfwLx2WX.css">
<script type="module" crossorigin src="/assets/index-yJqyT1FD.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-RonkRpMh.css">
</head>
<body>
<div id="app"></div>
+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
}
}