显示优化
This commit is contained in:
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
+72
-72
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -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
@@ -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