feat(*): 画布助手 - 新增 Live2D 配置导入与模型渲染支持
This commit is contained in:
29
web/src/lib/canvas/agent-appearance.ts
Normal file
29
web/src/lib/canvas/agent-appearance.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type CanvasAppearance = {
|
||||
agentName: string;
|
||||
launcherLabel: string;
|
||||
panelTitle: string;
|
||||
welcomeTitle: string;
|
||||
welcomeDescription: string;
|
||||
inputPlaceholder: string;
|
||||
avatarType: "orb" | "live2d";
|
||||
live2dResourceId: string;
|
||||
live2dEntry: string;
|
||||
avatarHeight: number;
|
||||
};
|
||||
|
||||
export const DEFAULT_CANVAS_APPEARANCE: CanvasAppearance = {
|
||||
agentName: "影策",
|
||||
launcherLabel: "Agent",
|
||||
panelTitle: "画布助手",
|
||||
welcomeTitle: "在这里,和{agentName}让灵感,慢慢成形",
|
||||
welcomeDescription: "从一个想法开始,和{agentName}一起创作。",
|
||||
inputPlaceholder: "输入操作指导;用 @ 引用画布节点,用 / 或 、 引用 Skills",
|
||||
avatarType: "orb",
|
||||
live2dResourceId: "",
|
||||
live2dEntry: "",
|
||||
avatarHeight: 220,
|
||||
};
|
||||
|
||||
export function agentCopy(template: string, name: string) {
|
||||
return template.replaceAll("{agentName}", () => name);
|
||||
}
|
||||
@@ -6,33 +6,33 @@ export const AGENT_LAUNCHER_POSITION_KEY = "canvas:agent-launcher-position:v1";
|
||||
export const AGENT_LAUNCHER_SIZE = 76;
|
||||
const MARGIN = 20; // Includes the label below the orb.
|
||||
|
||||
export function clampAgentLauncherPosition(position: AgentLauncherPosition, viewport: AgentLauncherViewport): AgentLauncherPosition {
|
||||
const clamp = (value: number, extent: number) => {
|
||||
const available = Math.max(0, extent - AGENT_LAUNCHER_SIZE);
|
||||
export function clampAgentLauncherPosition(position: AgentLauncherPosition, viewport: AgentLauncherViewport, size = { width: AGENT_LAUNCHER_SIZE, height: AGENT_LAUNCHER_SIZE }): AgentLauncherPosition {
|
||||
const clamp = (value: number, extent: number, dimension: number) => {
|
||||
const available = Math.max(0, extent - dimension);
|
||||
const margin = Math.min(MARGIN, available / 2);
|
||||
return Math.min(Math.max(value, margin), available - margin);
|
||||
};
|
||||
return { right: clamp(position.right, viewport.width), bottom: clamp(position.bottom, viewport.height) };
|
||||
return { right: clamp(position.right, viewport.width, size.width), bottom: clamp(position.bottom, viewport.height, size.height) };
|
||||
}
|
||||
|
||||
export function restoreAgentLauncherPosition(raw: string | null, viewport: AgentLauncherViewport): AgentLauncherPosition {
|
||||
export function restoreAgentLauncherPosition(raw: string | null, viewport: AgentLauncherViewport, size?: AgentLauncherViewport): AgentLauncherPosition {
|
||||
if (raw) {
|
||||
try {
|
||||
const value = JSON.parse(raw);
|
||||
if (value && Number.isFinite(value.right) && Number.isFinite(value.bottom)) return clampAgentLauncherPosition(value, viewport);
|
||||
if (value && Number.isFinite(value.right) && Number.isFinite(value.bottom)) return clampAgentLauncherPosition(value, viewport, size);
|
||||
} catch {
|
||||
// Damaged UI preferences must not hide the launcher.
|
||||
}
|
||||
}
|
||||
return clampAgentLauncherPosition({ right: MARGIN, bottom: MARGIN }, viewport);
|
||||
return clampAgentLauncherPosition({ right: MARGIN, bottom: MARGIN }, viewport, size);
|
||||
}
|
||||
|
||||
export function moveAgentLauncher(gesture: AgentLauncherGesture, x: number, y: number, viewport: AgentLauncherViewport) {
|
||||
export function moveAgentLauncher(gesture: AgentLauncherGesture, x: number, y: number, viewport: AgentLauncherViewport, size?: AgentLauncherViewport) {
|
||||
const dx = x - gesture.x;
|
||||
const dy = y - gesture.y;
|
||||
const dragged = gesture.dragged || Math.hypot(dx, dy) >= 6;
|
||||
return {
|
||||
dragged,
|
||||
position: clampAgentLauncherPosition(dragged ? { right: gesture.start.right - dx, bottom: gesture.start.bottom - dy } : gesture.start, viewport),
|
||||
position: clampAgentLauncherPosition(dragged ? { right: gesture.start.right - dx, bottom: gesture.start.bottom - dy } : gesture.start, viewport, size),
|
||||
};
|
||||
}
|
||||
|
||||
21
web/src/lib/canvas/live2d-core-adapter.ts
Normal file
21
web/src/lib/canvas/live2d-core-adapter.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
type CoreModel = {
|
||||
drawables: { count: number; renderOrders?: Int32Array };
|
||||
offscreens?: { count: number };
|
||||
getRenderOrders?: () => Int32Array;
|
||||
};
|
||||
|
||||
// Cubism 5.3 moved render orders to Model and added offscreen render objects.
|
||||
// Adapt only drawable-only models: the Cubism 4 renderer cannot draw offscreens.
|
||||
export function adaptLive2DRenderOrders(model: { getModel: () => CoreModel; getDrawableRenderOrders: () => Int32Array }) {
|
||||
const core = model.getModel();
|
||||
if (core.offscreens?.count) throw new Error("此模型使用新版离屏渲染,当前 Live2D 渲染器暂不支持");
|
||||
if (core.drawables.renderOrders) return;
|
||||
if (!core.getRenderOrders) throw new Error("Live2D Core 缺少绘制顺序接口,请检查运行库版本");
|
||||
const getOrders = () => {
|
||||
const orders = core.getRenderOrders!();
|
||||
if (orders.length !== core.drawables.count) throw new Error("Live2D 模型绘制顺序数量不匹配");
|
||||
return orders;
|
||||
};
|
||||
getOrders();
|
||||
model.getDrawableRenderOrders = getOrders;
|
||||
}
|
||||
Reference in New Issue
Block a user