显示优化
This commit is contained in:
+76
-21
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user