commit 72e7df09cddda2ad00a2ced8bd5aa1cf24129fce Author: zyc <1439655764@qq.com> Date: Tue May 12 11:14:10 2026 +0800 Initial commit: AR avatar prototype 包含三个子项目: - avatar-h5-renderer: Live2D Cubism 4 H5 渲染器 (Vite + TS) - avatar_flutter_app: Flutter 容器 App (打包 H5 进 WebView) - gif-export: puppeteer 导出 32 个动作的透明 GIF (供 ESP32 圆屏播放) 模型资源: Haru, Natori (含贴图、moc3、motions, expressions) 设计文档: AI驱动虚拟形象渲染方案_v5.1.md Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15aa9f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# ===== macOS ===== +.DS_Store +.AppleDouble +.LSOverride + +# ===== Editor / IDE ===== +.idea/ +.vscode/ +*.iml +*.ipr +*.iws +*.swp + +# ===== Logs / temp ===== +*.log +*.tmp +/tmp/ + +# ===== Node (avatar-h5-renderer, gif-export) ===== +node_modules/ +dist/ +.vite/ +.npm/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# ===== Flutter / Dart (avatar_flutter_app) ===== +**/build/ +**/.dart_tool/ +**/.flutter-plugins +**/.flutter-plugins-dependencies +**/.packages +**/.pub-cache/ +**/.pub/ +**/Pods/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/flutter_export_environment.sh +**/ios/Runner.xcworkspace/xcuserdata/ +**/ios/Runner.xcodeproj/xcuserdata/ +**/android/.gradle/ +**/android/local.properties +**/android/app/debug/ +**/android/app/profile/ +**/android/app/release/ +**/macos/Flutter/ephemeral/ +**/windows/flutter/ephemeral/ +**/linux/flutter/ephemeral/ + +# ===== Python ===== +__pycache__/ +*.pyc +*.pyo + +# ===== Project-specific large/intermediate files ===== +# Per-frame PNG dumps generated during GIF recording (intermediate only) +gif-export/clips/*_frames/ +# Texture backup created by black_to_transparent.py +**/texture_*.backup.png + +# ===== Secrets ===== +.env +.env.* +*.pem +*.key diff --git a/AI驱动虚拟形象渲染方案_v5.1.md b/AI驱动虚拟形象渲染方案_v5.1.md new file mode 100644 index 0000000..343e9e7 --- /dev/null +++ b/AI驱动虚拟形象渲染方案_v5.1.md @@ -0,0 +1,669 @@ +# AI 驱动虚拟形象渲染方案 v5.1 +## 跨产品形态统一架构调研 + +| 项目 | 内容 | +|---|---| +| 产品形态 | ① 罐子(Unity 3D + RK + 全息光仓,洛天依形象在跑)
② 洛天依 APP(独立 APP,Unity)
③ 小型硬件统一 APP(多设备入口,统一流量与用户)
④ ESP32-S3 小硬件(序列帧形态)
⑤ 未来 Live2D 形态 / 虚拟明星 / 写实数字人 | +| 当前底座 | **火山 RTC AIGC 全平台已跑通**(手机 / 罐子 / ESP32-S3)
整套 ASR + LLM + TTS 在云端打包,端到端延迟 < 2s | +| 当前痛点 | 形象层 3D 建模 + 手 K 动画,美术资产成本高,新对话/新场景都要手 K 衔接 | +| 调研目标 | 用 AI 驱动取代手 K,复用 RTC 已有底座,覆盖跨产品形态 | +| 调研日期 | 2026-05-09 | +| 适用主体 | 杰森气元科技 / 气元科技 | +| 优先级 | **P0 - 立即启动** | + +--- + +## 阅读指引 + +> 本文是给彭铭聪 / 张业昌 / 张文琦 review 的方案文档。Air 主导方向,**实操细节由你们决定**。 +> +> **核心思路**:RTC AIGC 已经原生下发字幕、AI 状态、情绪、Function Calling 等信号,Unity 端 / 各端在已有 RTC 回调里多解析这些事件,就能把动作衔接、表情切换、嘴型驱动从"手 K"切换到"AI 驱动"。 +> +> 罐子的洛天依 3D 模型、Animator、shader 全部保留,**不动**;只在事件消费层加东西。 +> +> 文档结构: +> - **第一~二章**:架构主线,先看 +> - **第三章**:罐子(洛天依)的具体落地路径,彭铭聪重点看 +> - **第四~六章**:其他产品形态参考,未来要做时再翻 +> - **第七章**:风险和待验证项 +> - **第八章**:P0 行动清单 +> - **第九章**:未来技术栈迁移路径(中长期成长方向,彭铭聪也建议看) + +--- + +## 一、关键结论 + +> **核心架构:协议层复用 RTC 信令,渲染层按形态分化。** +> +> 火山 RTC AIGC 已经把 ASR + LLM + TTS 整套打包跑在云端,TTS 输出的字幕(Subtitle)、AI 对话状态、情绪标签、Function Calling 事件**通过 RTC 房间内的标准消息回调下发到端侧**——跟音频流走同一条链路,天然时间对齐。 +> +> 各端(Unity / 手机 / RK / ESP32-S3)已有的 RTC SDK 已经具备接收这些消息的能力。**不需要自建 WebSocket 协议,不需要新连接管理**——只在已有 RTC 回调里加一层事件解析。 + +### 1.1 三件事必须分开讨论 + +很多技术争议来自把这三件事混在一起: + +| # | 概念 | 跑在哪 | 我们的方案 | +|---|---|---|---| +| 1 | **AI 推理**(LLM/TTS/ASR) | 云端 | 火山 RTC AIGC(已跑通) | +| 2 | **驱动信号下发** | 云端 → 端侧 RTC 信令 | 复用 RTC 已有通道 | +| 3 | **形象渲染**(每帧画面) | **端侧本地** | Unity / Cubism / 序列帧 | + +「AI 驱动」指 #1 输出的语义信号通过 #2 送到端侧,驱动 #3 自动渲染。**渲染本身仍在端侧本地跑**——跟所有 VTuber 软件、SEKAI、明日方舟一样的架构。 + +### 1.2 火山官方"数字人"功能 ≠ 我们要的方案,明确排除 + +| 维度 | 火山数字人 | 我们的需求 | +|---|---|---| +| 渲染位置 | 云端渲染,推 H264 视频流到端 | 端侧自渲染 | +| 形象库 | 火山预制 2D 真人 / 3D 超写实 / 3D 卡通 | 自建 3D(洛天依)/ 未来 Live2D / 序列帧 | +| 计费 | 按并发独立计费(付费资源) | 不需要,省钱 | +| 灵活度 | 形象由火山管 | 形象、动画、风格我们完全控制 | +| 商务限制 | 文档明确"提交工单咨询售前" | 标准 RTC AIGC 不需要额外谈判 | + +> **判定依据**:火山数字人文档明确"SubtitleConfig.SubtitleMode 必须设置为 1(不对齐时间戳)"——启用云端数字人时字幕只是显示用、不携带音频对齐信息(因为云端已经把口型烧进视频流了)。我们要的恰恰是 **SubtitleMode=0(对齐音频时间戳)**——给端侧自渲染用的字时间戳信号。 + +**判断技巧**:凡是火山文档里需要"独立计费 / 提交工单咨询 / 数字人 AppId / 并发限制"的能力——是云端付费功能。凡是 RTC AIGC 已经原生支持的能力(字幕、AI 状态、情绪、Function Calling)——是 RTC 标准能力,已经在订阅里。 + +**设计原则:只用 RTC 标准能力,不依赖任何独立付费资源**。 + +### 1.3 各产品形态可行性 + +| 形态 | 渲染技术 | 硬件 | 状态 | +|---|---|---|---| +| **罐子(洛天依)** | Unity 3D + BlendShape + Animator + 全息光仓 | RK3566 / RK3588S | **已在跑,下一步加 AI 驱动事件层** | +| **洛天依 APP**(独立) | Unity(与罐子同一份工程) | iOS / Android | 已有 demo,与罐子同步加 AI 驱动 | +| **小型硬件统一 APP** | Flutter + 多渲染层(按设备角色卡热加载) | iOS / Android | 待立项,**不用 Unity** | +| ESP32-S3 小硬件 | LVGL + sprite sheet 状态机 | ESP32-S3 | 方案已定,未来按 RTC 信令驱动 | +| 未来 Live2D 形态 | Cubism Native SDK | 待定 | 储备方案 | +| 未来虚拟明星 / 写实数字人 | Unity 或 Cocos / VRM 标准 | 高端硬件 / 手机 | 储备方案,见第九章 | + +### 1.4 立即启动的 P0 工作 + +**这是现在就要开始做的事**: + +1. 服务端在 RTC AIGC 配置里启用 SubtitleConfig,SubtitleMode 设为 0(对齐音频时间戳) +2. Unity 端打印 subtitle 消息 JSON,验证时间戳精度(决定后续嘴型驱动算法) +3. Unity 端最小可用 demo:罐子上洛天依 AI 对话时嘴型自动同步(取代当前手 K) + +详见第八章。**总工时 3-4 人天,本周可全部完成**。 + +--- + +## 二、修订后的数据流 + +```mermaid +sequenceDiagram + participant U as 用户 + participant App as 端侧 App
(Unity罐子/手机/RK/ESP32) + participant R as 本地渲染引擎
(Unity / Cubism / LVGL) + participant RTC as 火山 RTC
(全平台 SDK) + participant AIGC as 火山 AIGC 云
(ASR+LLM+TTS) + + Note over App,R: 模型/资产端侧本地常驻 + + U->>App: 说话 + App->>RTC: 音频上行 + RTC->>AIGC: 路由到 AIGC 服务 + AIGC->>AIGC: ASR → LLM(豆包) → TTS + + par 同一条 RTC 房间链路 + AIGC-->>RTC: TTS 音频流 + RTC-->>App: 订阅音频流 + and + AIGC-->>RTC: Subtitle 字幕(对齐时间戳) + RTC-->>App: onRoomMessageReceived + and + AIGC-->>RTC: AI 状态事件 / 情绪 / Function Call + RTC-->>App: onUserMessageReceived + end + + App->>App: 扬声器播放音频 + App->>R: 字时间戳 → 嘴型参数 + App->>R: 情绪 → 切换 expression + App->>R: Function Call → 触发 motion + R-->>U: 屏幕渲染 +``` + +**关键点**:所有 AI 输出走的是同一条 RTC 房间链路,跟音频流天然时间对齐。无独立 WebSocket,无独立连接管理,无新协议设计。 + +### 2.1 RTC AIGC 能用的信号(重要:直接对接列表) + +| 信号类型 | 来源 | 用途 | +|---|---|---| +| **字幕(Subtitle)** | TTS 流式输出 | 驱动嘴型(最关键) | +| **AI 状态事件** | 对话状态机 | 触发 idle / listening / thinking / speaking 状态切换 | +| **AI 对话任务事件** | 任务级回调 | 错误处理、对话开始/结束 | +| **情绪识别与生成** | 情绪模型输出 | 触发 expression 切换 | +| **Function Calling 结果** | LLM 工具调用 | 触发预设 motion / 业务动作 | +| **自定义 Message** | LLM 通过结构化输出 | LLM 主动插入 motion tag | + +这些能力**已经在你们的 demo 里跑通的 RTC SDK 里**——不需要任何新连接。 + +### 2.2 端侧通用消费模式 + +每个端的 RTC SDK 都有这两类回调: + +``` +onRoomMessageReceived(msg) // 房间广播消息(字幕通常走这里) +onUserMessageReceived(uid, msg) // 点对点消息(AI 状态事件通常走这里) +``` + +端侧实现 = 在这两个回调里加一层 JSON 解析 → 写入时间轴消费器 → 驱动渲染层。 + +### 2.3 端侧时间轴示例 + +```mermaid +gantt + title 端侧 1 秒内的渲染时间轴:「今天天气真好」 + dateFormat X + axisFormat %Lms + + section 音频播放 + PCM 解码播放 :a1, 0, 1000 + + section 嘴型 (字 → viseme) + 今 (j-in) :p1, 0, 200 + 天 (t-ian) :p2, 200, 400 + 是 (sh-i) :p3, 400, 600 + 好 (h-ao) :p4, 600, 1000 + + section 表情 + expression= happy :e1, 0, 1000 + + section 动作 + motion= idle.smile :m1, 0, 1000 +``` + +端侧只是一个**时间轴消费器**:音频播放线程独立、嘴型按字时间戳改 BlendShape、表情按 emotion 区间切换、motion 按 Function Call 触发。 + +### 2.4 「渲染跑在端侧」是工业标准 + +需要向团队说明的一件事——这不是新架构,是行业惯例: + +- 全球所有 VTuber 直播软件(VTube Studio、PrprLive、Animaze)都是端侧本地跑 +- Project SEKAI、明日方舟、原神信箱里的角色,端侧本地渲染 +- Unity 跑在 Android RK 板上是产业标准做法(车载、零售、家电常见) + +**「AI 驱动虚拟人」的新意不在「跑在云端」,而在驱动信号源**: + +| 传统形象渲染 | AI 驱动 | +|---|---| +| 主播说话 | LLM 输出文本 | +| 主播按表情按钮 | LLM 输出 emotion tag | +| 摄像头面捕跟踪嘴型 | TTS 输出字时间戳 | +| 主播按动作快捷键 | LLM 输出 Function Call | +| 动画师手 K 衔接 | RTC AI 状态事件驱动状态机 | + +--- + +## 三、罐子(洛天依)落地路径 + +**这是 P0 的核心工作。彭铭聪重点看本章。** + +### 3.1 当前状态梳理 + +- Unity 工程已有洛天依 3D 模型(手动建模) +- 动画师做了多套动作,彭铭聪在 Unity 里做动作衔接 +- 已经加了"张嘴"——但应该不是真正的 viseme 嘴型,是简化版张/合(Q 版卡通形象上够用) +- RTC + AI 对话已经跑通 + +### 3.2 不变的部分 + +- 3D 建模 ✅ 保留 +- BlendShape / 骨骼绑定 ✅ 保留 +- 现有 Animator 和动作衔接 ✅ 保留 +- shader 和全息光仓适配 ✅ 保留 +- RTC 集成 ✅ 保留 + +### 3.3 要加的部分 + +在 Unity 工程里加一个事件消费层: + +``` +[业务逻辑层] ← 纯 C# 逻辑,不绑 Unity API,可移植 + ├─ RTCMessageParser.cs // RTC 消息 JSON 解析 + ├─ TimelineConsumer.cs // 事件队列 + 时间轴消费 + └─ CharacterStateMachine.cs // 角色状态抽象(idle/listening/speaking...) + ↓ 通过接口调用 +[渲染适配层] ← 绑 Unity,未来换技术栈时只重写这一层 + └─ UnityCharacterRenderer.cs // BlendShape / Animator 写入 + ↓ +[已有 Unity 资产] ← 完全不动 + ├─ 洛天依 3D 模型 + ├─ Animator 状态机 + └─ shader / 全息光仓适配 +``` + +**关键设计原则:业务逻辑层和渲染适配层解耦**。具体做法: +- 业务逻辑层不直接调 `Animator.SetTrigger()` 或 `SkinnedMeshRenderer.SetBlendShapeWeight()` +- 改成调一个接口 `ICharacterRenderer`,方法是 `SetMouthOpen(float v)` / `PlayMotion(string name)` / `SwitchExpression(string name)` 这种语义化 API +- Unity 实现 `UnityCharacterRenderer : ICharacterRenderer` +- 未来想换 Cocos / Three.js / 其他,只重写一个 Renderer 实现,业务逻辑零改动 + +> **这个分层在当下不显眼,未来换技术栈时值千金**。详见第九章「未来技术栈迁移路径」。 + +完整 Unity 工程结构: + +``` +[Unity 主线程] + ├─ RTCEngine(已有) + │ └─ OnRoomMessageReceived / OnUserMessageReceived ← 转发给 RTCMessageParser + ├─ 业务逻辑层(新增,纯 C#) + │ ├─ RTCMessageParser + │ ├─ TimelineConsumer + │ └─ CharacterStateMachine + ├─ 渲染适配层(新增) + │ └─ UnityCharacterRenderer : ICharacterRenderer + └─ AudioPlayback(已有,作为时间基准) +``` + +**改动量很小**,3D 资产和现有动画状态机一行不动。 + +### 3.4 全息光仓适配 + +LCD 拆背光后,黑色像素 = 透明(光不发出去),亮色像素 = 发光。渲染要求: + +- **纯黑背景**(不要天空盒,不要环境光) +- **角色用发光材质**:emission map + Bloom 后处理强化 +- **避免大面积低亮度区域**(在光仓里看不见,会显得角色少了一块) +- **轮廓光强化**:rim light 让角色边缘更清晰 +- **慎用半透明**:alpha blend 在拆背光后效果难预测,先做实验 +- **色彩偏向高饱和、亮色调**(青色/紫色/品红在全息感下最强) + +这些规则彭铭聪应该已经在做了,列在这里供未来其他人参考。 + +### 3.5 RK 性能预期 + +| 板子 | 屏幕 | 单角色 PBR | Unity URP | 全息 shader | +|---|---|---|---|---| +| RK3566 Mali-G52 2EE | 720p | 30fps(顶点 < 30k) | URP 简化版 | 简单 Bloom | +| RK3588S Mali-G610 | 1080p | 60fps 轻松 | URP 完整 | Bloom + 体积光可选 | + +**RK3566 优化注意点**: +- 顶点数严控(< 30k),骨骼 < 50 +- shader 不要用复杂 PBR,半 Lambert + matcap + 发光通道即可 +- URP 关闭 SSR / SSAO / DOF;Bloom 用低质量档 +- 720p 是合理目标,强求 1080p 会掉帧 + +### 3.6 关于嘴型精度的讨论 + +> Air 提到:"洛天依现在嘴型只是张/合,不是真嘴型,对 Q 版卡通形象其实够用。但未来做虚拟明星、真人写实数字人时,真嘴型才比较重要。" + +这个判断对: + +| 形象类型 | 嘴型精度要求 | 实现路径 | +|---|---|---| +| Q 版卡通(洛天依) | 张/合 + 表情即可 | 字时间戳 → 1 个 BlendShape 开合度,足够 | +| 写实/半写实虚拟艺人 | 6 viseme(A/I/U/E/O/sil) | 字 → viseme → 5+ 个 BlendShape 加权 | +| 顶级写实数字人 | 15 viseme + 协同发音 | 接 NVIDIA Audio2Face 或类似方案 | + +**当前阶段(洛天依罐子)做最简实现就够**:字时间戳到了 → BlendShape 开合度 = 1(讲话中);字时间戳间隙 → 开合度 = 0;用 LERP 平滑过渡。一个 BlendShape 解决问题。 + +未来做虚拟明星(CYBER STAR 那条线)时,再升级到完整 viseme 集,**那时候方案不变,只是 CharacterController 里多写几行映射代码**。 + +### 3.7 AI 驱动的真正价值不只在嘴型 + +**这里要纠正一个可能的认知偏差**:洛天依嘴型确实不重要,但这不等于 AI 驱动方案对洛天依不重要。 + +AI 驱动信令的真正价值在**动作 / 表情 / 状态切换**: + +- 不再手 K 每段对话的动作衔接 +- AI 说话内容触发情绪 → 自动切表情 +- LLM 输出 Function Call → 自动播预制动作(挥手、点头、托腮、唱歌起手...) +- idle / listening / thinking / speaking 状态机由 RTC AI 状态信号驱动 + +**美术资产成本无限堆高的核心问题,是动作和动画**,不是嘴型。彭铭聪现在每加一段新对话就要手 K 衔接的痛点,AI 驱动信令解决的就是这个。 + +罐子上**最容易出效果的不是嘴型**,是: +1. AI 在思考时(状态 = thinking)→ 自动播"歪头思考"动作 +2. AI 在听用户说话时(状态 = listening)→ 自动播"侧耳倾听"动作 +3. AI 说到开心内容时(情绪 = happy)→ 自动切笑脸 + 播"拍手"动作 +4. 用户问"你会唱歌吗",LLM 触发 Function Call `play_song` → 播预录的唱歌动画 + +这些都是预制好的 motion clip,AI 只是按语义触发——美术做一次,无限复用。 + +--- + +## 四、其他产品形态参考 + +> 这一章是知识储备。当前不需要立即用,但未来做新产品时直接拿来用,不用重新调研。 + +### 4.1 当前手机 APP 架构 + +**两个 APP 分立**: + +| APP | 形态 | 当前技术栈 | 维护人 | +|---|---|---|---| +| **洛天依 APP**(独立) | 单 IP 独立产品,3D 形象 | **Unity**(与罐子同一份工程) | 彭铭聪 | +| **小型硬件统一 APP** | 多产品入口,承载 ESP32-S3 等小硬件 | 原生(Flutter / 待定) | TBD | + +**为什么分立**: +- 洛天依是单一 IP 产品,沉浸式体验,Unity 重资产合理 +- 小型硬件整合成一个 APP 是为了**统一流量入口和用户管理**,调性轻快,不适合塞 Unity 这种重容器 + +### 4.2 小型硬件统一 APP 的渲染方案 + +按硬件对应的形象类型,APP 内嵌不同渲染层: + +``` +[小型硬件统一 APP(Flutter 推荐)] + ├─ 用户中心 / 流量入口 / 统一登录 + ├─ 设备管理 / 配网 / 固件升级 + ├─ 多产品入口(卡皮巴拉、桌面机器人...) + └─ 形象渲染层(按设备角色卡热加载) + ├─ Cubism Native(如果设备角色是 Live2D) + ├─ WebView + three.js + three-vrm(如果是 VRM 3D 形象) + └─ SpriteRenderer(如果是序列帧形象,与 ESP32-S3 资产一致) + └─ 火山 RTC Flutter SDK(已有) +``` + +**关键决策**:小型硬件 APP **不用 Unity**——理由: +- APP 体积要小,启动要快(用户用完即走的轻量场景) +- 形象层多样化,要能热切换不同渲染方案 +- Unity 编出的 APP 包大、启动慢,跟"流量入口 + 用户管理"调性不符 + +### 4.3 洛天依 APP 的演进 + +当前 Unity 工程在跑,下一步同步加 AI 驱动事件层(与罐子代码尽量复用)。 + +未来可能的演进方向: +- 短期(2026):保持 Unity,专注 AI 驱动改造 +- 中长期:见第九章「未来技术栈迁移路径」 + +### 4.4 Live2D 形态(未来) + +如果未来某个产品定位需要 Live2D(2D Q 版陪伴角色、复古二次元风格): + +- **渲染**:Cubism Native SDK Android Java / iOS Objective-C++ +- **嘴型驱动**:字时间戳 → `ParamMouthOpenY` / `ParamMouthForm` +- **资产生产**:外包 Live2D 师,5k-3w/形象(2 头身偏便宜) +- **商用授权**:见第六章 + +### 4.5 3D / VRM 路径(未来虚拟明星 / 写实数字人) + +**适用场景**:未来立项的独立 IP 产品(如 CYBER STAR 虚拟艺人)、批量生产的标准化 3D 形象、jalab.ai 网页端展示。**不替代**罐子/洛天依 APP 现有 Unity 路径。 + +如果未来要做: +- 全身 / 360°视角的形象 +- 多角色复用(VRoid Studio 出 VRM,pixiv 标准) +- 跨产品形象互通 + +**三条集成路径**: + +| 路径 | 出活速度 | 性能 | 推荐场景 | +|---|---|---|---| +| **A. WebView + three.js + three-vrm** | 快(1-2 周) | 手机 60fps | 默认起步,可嵌入小型硬件统一 APP | +| **B. Unity as a Library + UniVRM** | 慢(4-6 周搭基建) | 最好 | 3D 是核心体验、独立 APP | +| **C. Filament(thermion)** | 中 | 好 | 2027 年再看 | + +**Path A** = three-vrm 是 pixiv 官方维护,VRM 标准 blend shape / SpringBone / LookAt 全部内置。**这条路径与第 4.2 节的小型硬件统一 APP 渲染层兼容**——VRM 形象可以通过 WebView 容器嵌进去。 + +**重要澄清**: +- 洛天依**不需要**切到 VRM——Unity 自建模型已经在跑,继续用 +- VRM 是给**未来批量生产 3D 形象**时用的标准,不是替代当前路径 + +### 4.6 序列帧(ESP32-S3) + +ESP32-S3 没有 GLES 跑不了 Live2D / 3D。方案: + +- 云端按形象 ID 预渲染 sprite sheet(emotion × 4-8 帧 + motion × 多个) +- 端侧 LVGL 状态机消费 emotion / motion frame +- **忽略字时间戳精度**(用音频包络替代驱动嘴型 3 档:闭/半开/全开) +- 简化下行带宽 + +**关键简化**:火山 RTC ESP32-S3 SDK 已经把信令回调暴露出来——不需要自写 mbedTLS WebSocket Client,直接挂回调即可。预估 C 代码量 < 200 行。 + +--- + +## 五、目标硬件能力参考 + +| 项目 | CPU | GPU | NPU | 含义 | +|---|---|---|---|---| +| **RK3566** | 4× A55 @2.0GHz | Mali-G52 2EE | 1 TOPS | 跑单 Unity 3D 角色 + 简单 UI 可,720p 30fps | +| **RK3568** | 4× A55 @2.0GHz | Mali-G52 2EE(同 3566) | 0.8 TOPS | 工业版,外设丰富,渲染等价 | +| **RK3588S** | 4× A76 + 4× A55 | Mali-G610 MC4 | 6 TOPS | 1080p 60fps 轻松,可叠加端侧 ASR/VAD | +| 中端手机 | 骁龙 6/7 系或天玑 7/8 系 | Adreno 6xx / Mali-G68+ | >2 TOPS | 性能远超需求 | + +> **命名澄清**:Rockchip 没有「RK3568S」型号。RK3566 是消费级,RK3568 是工业级,两者 GPU 同款(Mali-G52 2EE 双核)。 + +--- + +## 六、Live2D 商用授权评估(未来用得上时再翻) + +> 当前不立即用,但 Live2D 做商业产品需要提前 6 个月走法务流程。储备信息。 + +Cubism SDK 商用必须签 Publication License Agreement。 + +### 6.1 企业规模分级(按年销售额) + +| 分级 | 年销售门槛(日元) | 约合人民币 | +|---|---|---| +| General User(个人) | < 1000 万 | < 50 万 RMB | +| Small(小企业) | < 1000 万 | 免授权费(部分 AI App 除外) | +| Middle(中企业) | 1000 万 ~ 1 亿 | 50 万 ~ 500 万 RMB | +| Large(大企业) | ≥ 1 亿 | ≥ 500 万 RMB | + +> ⚠️ 杰森气元和火山 1000 万 CNY/年 框架已签,按汇率约合 2 亿日元,已达 Large-Scale。需要法务介入正式评估。 + +### 6.2 AI / Chatbot 应用:Expandable Application 特殊审核 + +Live2D 官方明确:「使用 Cubism SDK 作为 AI 或 chatbot 接口的内容」需要走 Expandable Application 流程,**需要 Live2D Inc. 单独审核批准并签订专属 Publication License**。情感陪伴 Agent / 虚拟伴侣类产品**不能套用普通 plan**。 + +### 6.3 费用结构参考(Running Royalty Plan) + +- **Large-Scale**:初始费 30 万日元/Region;月费 10 万日元/平台/Region(iOS / Android / HarmonyOS / Web 各算一个平台) +- **Middle-Scale**:初始费 5 万日元/Region;月费 2 万日元/平台/Region +- 显示 Live2D logo + Showcase 列出可享折扣价 + +> **建议**:PoC 阶段不走商用 license,先确认技术路径和形象效果。商业化前 6 个月让法务联系 Live2D 中国区代理,按 Expandable Application 流程提交方案。 + +--- + +## 七、风险与待验证项 + +修订后剩下的不确定项收敛到很少: + +### 7.1 必须 PoC 验证的(决定算法选型) + +**SubtitleMode=0 实际下发的时间戳精度**: +- 是字级(每个字带 begin_ms / end_ms)?还是只到句级? +- 时间戳与音频流的实际对齐误差(理想 < 50ms) + +**验证方法**(半天工作量): +1. 服务端启用 SubtitleMode=0 调用 StartVoiceChat +2. 端侧打印每个 subtitle 消息的完整 JSON +3. 同时录音频流 +4. 用 Audacity 对比字幕时间戳与音频实际发声时间 + +**结果分支**: +- **结果 A:字级对齐**(最佳)→ 直接驱动嘴型,精度满足 lip sync +- **结果 B:只到句级** → 端侧加音量包络辅助:句级时间戳确定开始/结束,音量包络驱动开合度 +- **结果 C:完全不返回时间戳** → 退回纯音量驱动(精度仍够洛天依的 Q 版张合用) + +无论哪种结果,**整体架构都不变**,只是端侧驱动算法层换实现。 + +### 7.2 实操中需要彭铭聪 / 业昌 / 文琦决定的 + +- 现有 Animator state 能否被 RTC AI 状态事件直接驱动?(需要 review 现有状态机设计) +- Function Calling 触发预制 motion 的工程实现路径(在 Unity 端怎么 dispatch) +- 第 3.3 节 `ICharacterRenderer` 接口的具体方法集和签名(这是未来跨引擎可移植性的关键) +- 全息光仓 shader 在 RK3566 上的实测帧率和热表现 + +### 7.3 已被消除的不确定项(不再需要验证) + +- ~~豆包 TTS 是否独立返回 phoneme 时间戳~~ → RTC AIGC 把 TTS 包了,我们用 Subtitle +- ~~自建 WebSocket 协议设计~~ → 不需要,复用 RTC 信令 +- ~~端侧自管理连接、心跳、重连~~ → RTC SDK 已处理 +- ~~phoneme 编码 → IPA 映射表~~ → Subtitle 直接出可读文本 + +### 7.4 长期风险 + +- **Live2D Expandable Application 审核结果不确定**(未来用 Live2D 时再面对) +- **Live2D license 费用对中型企业较高**(同上) +- **Open-LLM-VTuber license 变更**(如果未来参考其架构需要注意 v1.2 之前是 MIT) +- **Unity 中国长期不确定性**(详见第九章)——这是 1-3 年维度的战略风险,当下通过分层架构防御 + +--- + +## 八、P0 行动清单 + +### 8.1 本周启动(5 月 12 日 ~ 5 月 16 日) + +| # | 任务 | 负责人 | 工时 | 交付 | +|---|---|---|---|---| +| **P0-1** | 服务端 RTC AIGC 启用 SubtitleConfig,SubtitleMode=0 | 罐子组 / 业昌 | 0.5 天 | StartVoiceChat 配置就绪 | +| **P0-2** | Unity 端打印 subtitle 消息 JSON,验证时间戳精度 | 罐子组 | 0.5 天 | 时间戳精度报告(5 测试句对齐误差) | +| **P0-3** | Unity 端 TimelineConsumer + BlendShape 嘴型驱动 demo | 彭铭聪 | 1-2 天 | 罐子上洛天依 AI 对话时嘴型自动同步 | +| **P0-4** | Unity 端 RTC AI 状态事件 → Animator state 桥接 | 彭铭聪 | 1 天 | listening/thinking/speaking 自动切动作 | + +**总工时**:3-4 人天,本周内可全部完成。 + +**P0 完成后的可见效果**:罐子上现有洛天依 3D 模型,AI 对话时嘴型自动驱动 + 状态自动切动作,**无需任何手 K 嘴型/动作衔接**。这就是从"美术资产成本无限堆高"切换到"AI 驱动"的关键拐点。 + +### 8.2 P1 - 2 周内 + +- 情绪事件接入:RTC AIGC 情绪信号 → Animator BlendTree(happy/sad/surprised...) +- Function Calling 触发预设 motion:定义第一批工具(挥手、点头、托腮、唱歌起手) +- 洛天依 APP 同步获得 AI 驱动能力(与罐子是同一份 Unity 工程,自动获益) +- 全息光仓适配在 RK3566 / RK3588S 上量化帧率 + +### 8.3 P2 - 1 个月内 + +- 端侧 SDK 抽象:把事件消费层封装成可复用模块(Unity Package / 各端 lib) +- 形象资产规范:motion 命名 / expression 集合 / BlendShape 标准化(为未来批量产形象做准备) +- ESP32-S3 sprite sheet 云端预渲染管线设计 + +### 8.4 未来项(按需启动) + +- Live2D 商用授权流程启动(决定要做 Live2D 产品时提前 6 个月) +- VRM 标准接入(决定要批量 3D 形象时) +- 虚拟明星形象的高精度 viseme 升级(CYBER STAR 立项时) +- 端侧 Whisper + VAD 打断(RK3588S 高端方案,提升交互自然度) + +--- + +## 九、未来技术栈迁移路径 + +> 本章是给彭铭聪等核心开发者的中长期成长方向。Air 与彭铭聪已就此有过深谈:**核心开发者要跟着团队成长,不能永远只做 Unity**。本章把可能的迁移方向和触发条件写清楚,作为长期参考。 + +### 9.1 为什么要考虑迁移 + +Unity 当下是最优解,但 1-3 年维度有几个不确定项: + +- **Unity 中国(团结引擎)独立运营路径不明朗**——版本分裂、定价模型、政策影响、海外版功能同步存在变数 +- **Unity 商业模式变化历史**:2023 年的 Runtime Fee 风波说明商业政策可能突变 +- **国产引擎政策导向**:信创、国产替代趋势可能影响 ToB 产品选型 +- **Web 化与跨端化趋势**:Three.js / 小程序 3D / 浏览器 WebGPU 在变强,移动端原生不再是唯一答案 + +**我们不需要现在就换,但需要现在就让架构能换**。 + +### 9.2 触发迁移的信号 + +出现以下任一情况,启动迁移评估: + +1. Unity 中国版收费模式或政策对我们形成实质成本/合规压力 +2. 跨端复用需求超出 Unity 能力边界(小程序、Web、HarmonyOS NEXT 等) +3. 国产化要求(ToB 项目、政府采购场景)明确排除海外引擎 +4. 团队增长后多人维护 Unity 工程的协作成本超过迁移成本 +5. 出现某个新形象类型,Unity 不是最优解(如 Live2D / VRM 标准 / 复杂 Web 场景) + +### 9.3 候选技术栈对比 + +| 候选 | 适配场景 | 优势 | 劣势 | 切换难度 | +|---|---|---|---|---| +| **Cocos Creator 4** | 主推备选 | 国产、政策最稳;团队已有 Cocos 能力(Avatar Social 在用);3D 能力够用 | 3D 生态不如 Unity 成熟;shader / 物理 / 后处理需重写 | 中(资产可部分复用,逻辑层完全可移植) | +| **Three.js + three-vrm** | 走 Web / 跨端轻量化 | 跨平台、Web 化、VRM 标准化、生态活跃 | 性能不如原生;复杂角色掉帧;需要 WebView 容器 | 低(如果走 VRM 标准) | +| **Godot 4** | 完全开源场景 | MIT 开源、免费、跨平台 | 国内生态弱、找开发者难、3D 工具链一般 | 中 | +| **Unreal Engine** | 顶级画质场景 | 顶级 PBR / 物理 / 影视级渲染 | 包体大、移动端 / 嵌入式不友好;学习曲线陡 | 高 | +| **自研 + Three.js / Filament** | 完全可控 | 100% 自主 | 投入巨大,不适合现阶段 | 不考虑 | + +**主推备选是 Cocos Creator 4**: +- Avatar Social 项目团队已经在用,能力沉淀可复用 +- 国产引擎政策最稳 +- 资产工作流(建模 → 导入 → 动画)和 Unity 接近,迁移学习曲线低 +- 嵌入式(罐子 RK 板)和手机都能跑 + +**Three.js / three-vrm 是另一类备选**: +- 不是替代 Unity 做主力,而是补充 +- 跨端 Web 容器 / 小程序 / VRM 标准角色 用它最合适 +- 未来如果做 jalab.ai 网页端虚拟艺人展示,必然走 Three.js + +### 9.4 迁移成本估算(以 Cocos 为例) + +按"洛天依完整迁移到 Cocos"测算: + +| 项 | 工作量 | 备注 | +|---|---|---| +| 3D 资产重新导入 + 材质适配 | 1-2 周 | FBX 可直接导入,shader 要重写 | +| Animator 状态机重建 | 1 周 | Cocos animation system 不同 | +| 全息光仓 shader 重写 | 1 周 | 后处理管线不同 | +| 业务逻辑层接入 | 0.5 天 | **如果做了第 3.3 节的分层,这一步几乎零成本** | +| RTC SDK 适配 | 0.5 天 | 火山 RTC Cocos 集成已有 | +| 联调与优化 | 1-2 周 | 性能调优、bug 修 | +| **总计** | **4-6 周** | 一个工程师全职 | + +**关键洞察**:业务逻辑层(RTC 解析、TimelineConsumer、CharacterStateMachine)的迁移成本几乎为零——因为它们是纯 C# 逻辑,不绑 Unity API。**前提是第 3.3 节的分层设计严格执行**。 + +### 9.5 给彭铭聪的成长方向 + +短期(2026 年): +- 把洛天依 Unity 工程做深做透 +- AI 驱动事件层是这一年的核心 milestone +- 通过分层设计积累"引擎无关"的工程思维 + +中期(2027 年): +- 学习 Cocos Creator 4,参与 Avatar Social 等 Cocos 项目交叉协作 +- 评估某个新产品形态用 Cocos 实现的可能性 +- 主导一次小规模技术栈迁移试点(不动洛天依,从新立项的小产品开始) + +长期(2-3 年后): +- 成为"形象渲染层"的技术决策者,能根据产品需求选最合适的引擎 +- 不被 Unity 这一个工具锁死,技能围绕"3D / 实时渲染 / 角色驱动"这个领域而非"Unity 这个引擎" + +**这是一个工程师从"工具使用者"到"领域专家"的转变路径**。 + +### 9.6 当下要做的事 + +1. ✅ **架构上做防御**:第 3.3 节的分层设计严格执行 +2. ✅ **业务逻辑不绑引擎 API**:所有渲染调用走 `ICharacterRenderer` 接口 +3. ⚠️ **不要现在迁移**:当前痛点是美术资产成本,不是引擎选型,先做完 P0 AI 驱动改造 +4. ⚠️ **不要在两个引擎间来回切**:决定迁移时一次切换到位,不要并行维护两套 + +--- + +## 附录 A:参考项目链接 + +**核心参考(直接对接)**: +- 火山 RTC AIGC Demo(重点参考): +- 火山 RTC AI 实时字幕文档: +- 火山 RTC 获取 AI 状态文档: +- 火山 RTC 情绪识别与生成: +- 火山 RTC Function Calling: +- 火山 RTC 嵌入式硬件集成: + +**反例(已排除,仅作判断依据)**: +- 火山 RTC AI 数字人形象文档: ← 这是云端渲染推视频流方案,**我们不用这个**,但通过它的文档反推出 SubtitleMode=0 是给端侧渲染用的 + +**储备方案参考**: +- Cubism SDK 下载: +- Cubism License: +- three-vrm(pixiv 官方): +- TalkingHead(VRM lipsync 参考): +- Open-LLM-VTuber(参考架构): +- Cocos Creator 4 文档: + +--- + +## 附录 B:术语速查 + +- **BlendShape / Morph Target**:3D 模型的形变权重,控制嘴型 / 表情 / 五官变化 +- **Animator / Mecanim**:Unity 的状态机动画系统 +- **Viseme(视位)**:发音对应的视觉嘴型,多对一映射音素 +- **Subtitle**:火山 RTC AIGC 下发的字幕事件,包含字内容和时间戳 +- **SubtitleMode**:火山 RTC 配置项,0 = 对齐音频时间戳(端侧渲染用),1 = 不对齐(云端数字人用) +- **Function Calling**:LLM 调用工具/函数的能力,可被业务端解析触发动作 +- **Cubism**:Live2D 公司的 SDK 品牌 +- **VRM**:基于 glTF 的 3D 角色文件标准,pixiv 主导 +- **全息光仓**:LCD 拆背光后的伪全息显示形态,黑像素=透明,亮像素=发光 +- **Expandable Application**:Live2D 官方对 AI/Chatbot 类应用的特殊分类,需单独审核签约 diff --git a/avatar-h5-renderer/copy_resources.js b/avatar-h5-renderer/copy_resources.js new file mode 100644 index 0000000..cd1105c --- /dev/null +++ b/avatar-h5-renderer/copy_resources.js @@ -0,0 +1,6 @@ +/** + * Resources are already copied during initial setup into ./public/. + * This script is a no-op kept for npm script compatibility. + */ +"use strict"; +console.log('[copy_resources] Resources already populated in ./public/.'); diff --git a/avatar-h5-renderer/eslint.config.mjs b/avatar-h5-renderer/eslint.config.mjs new file mode 100644 index 0000000..815232c --- /dev/null +++ b/avatar-h5-renderer/eslint.config.mjs @@ -0,0 +1,168 @@ +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import globals from 'globals'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import eslintPluginPrettier from 'eslint-plugin-prettier'; + +export default tseslint.config( + eslint.configs.recommended, + tseslint.configs.eslintRecommended, + ...tseslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + eslintConfigPrettier, + { + languageOptions: + { + parserOptions: + { + sourceType: 'module', + ecmaVersion: 2020, + project: './tsconfig.json', + }, + globals: + { + ...globals.browser, + }, + }, + plugins: + { + 'prettier': eslintPluginPrettier, + }, + rules: + { + 'prettier/prettier': [ + 'error', + { + singleQuote: true, + trailingComma: 'none', + arrowParens: 'avoid', + } + ], + camelcase: 'off', + '@typescript-eslint/naming-convention': [ + 'warn', + { + selector: 'default', + format: ['camelCase'], + }, + { + selector: 'import', + format: ['PascalCase'], + }, + { + selector: 'variable', + format: [], + custom: { + // 指定の文字列で始まるものと特定の文字を含むものは許容 + regex: '^[A-Z]|^csm|^iterator|Shader', + match: true, + }, + modifiers: ['exported', 'const'], + }, + { + selector: 'variable', + format: ['camelCase'], + }, + { + selector: 'variable', + format: [], + custom: { + // 指定の文字列で始まるものは許容 + regex: '^[A-Z]|^s_', + match: true, + }, + modifiers: ['global'] + }, + { + selector: 'enum', + format: ['PascalCase'], + }, + { + selector: 'enumMember', + format: [], + custom: { + // 大文字から始まること + regex: '^[A-Z]', + match: true, + } + }, + { + selector: 'classProperty', + format: ['PascalCase'], + modifiers: ['static', 'readonly'] + }, + { + selector: 'classProperty', + format: ['camelCase'], + leadingUnderscore: 'allow', + }, + { + selector: 'class', + format: [], + custom: { + // 指定の文字列で始まるか、指定の文字列で終わること + regex: '^[A-Z]|^csm|^iterator|_WebGL$', + match: true, + } + }, + { + selector: 'interface', + format: ['camelCase', 'PascalCase'], + }, + { + selector: 'parameter', + format: ['camelCase'], + }, + { + selector: 'classMethod', + format: ['camelCase'], + }, + { + selector: 'objectLiteralProperty', + format: ['camelCase', 'PascalCase'], + }, + { + selector: 'typeAlias', + format: [], + custom: { + // 指定の文字列で始まるものは許容 + regex: '^[A-Z]|^[a-z]|^CSM_|^csm|^iterator', + match: true, + }, + modifiers: ['exported'] + }, + { + selector: 'typeAlias', + format: ['camelCase'], + }, + { + selector: 'typeParameter', + format: [], + custom: + { + // 「大文字+アンダースコア以外の文字」、あるいは「大文字1文字」 + // あるいは、「`T`+アンダースコア」で始まる場合 + regex: '^[A-Z][^_]|^[A-Z]|^T_$', + match: true, + }, + leadingUnderscore: 'allow', + }, + ], // @typescript-eslint/naming-convention + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, + }, + { + // ignores property はなぜか単独で指定していないと効果がない。 + ignores: [ + '**/*.*', + '!src/**/*.ts', + ], + }, +); diff --git a/avatar-h5-renderer/framework/package.json b/avatar-h5-renderer/framework/package.json new file mode 100644 index 0000000..56ee61b --- /dev/null +++ b/avatar-h5-renderer/framework/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "scripts": { + "build": "tsc", + "test": "tsc --noEmit", + "lint": "eslint", + "lint:fix": "eslint --fix", + "clean": "rimraf dist" + }, + "devDependencies": { + "@eslint/js": "^9.39.2", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "prettier": "^3.8.1", + "rimraf": "^6.1.3", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.2" + } +} diff --git a/avatar-h5-renderer/framework/src/cubismdefaultparameterid.ts b/avatar-h5-renderer/framework/src/cubismdefaultparameterid.ts new file mode 100644 index 0000000..1d716d5 --- /dev/null +++ b/avatar-h5-renderer/framework/src/cubismdefaultparameterid.ts @@ -0,0 +1,118 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * @brief パラメータIDのデフォルト値を保持する定数
+ * デフォルト値の仕様は以下のマニュアルに基づく
+ * https://docs.live2d.com/cubism-editor-manual/standard-parametor-list/ + */ +export const CubismDefaultParameterId = Object.freeze>({ + // パーツID + HitAreaPrefix: 'HitArea', + HitAreaHead: 'Head', + HitAreaBody: 'Body', + PartsIdCore: 'Parts01Core', + PartsArmPrefix: 'Parts01Arm_', + PartsArmLPrefix: 'Parts01ArmL_', + PartsArmRPrefix: 'Parts01ArmR_', + // パラメータID + ParamAngleX: 'ParamAngleX', + ParamAngleY: 'ParamAngleY', + ParamAngleZ: 'ParamAngleZ', + ParamEyeLOpen: 'ParamEyeLOpen', + ParamEyeLSmile: 'ParamEyeLSmile', + ParamEyeROpen: 'ParamEyeROpen', + ParamEyeRSmile: 'ParamEyeRSmile', + ParamEyeBallX: 'ParamEyeBallX', + ParamEyeBallY: 'ParamEyeBallY', + ParamEyeBallForm: 'ParamEyeBallForm', + ParamBrowLY: 'ParamBrowLY', + ParamBrowRY: 'ParamBrowRY', + ParamBrowLX: 'ParamBrowLX', + ParamBrowRX: 'ParamBrowRX', + ParamBrowLAngle: 'ParamBrowLAngle', + ParamBrowRAngle: 'ParamBrowRAngle', + ParamBrowLForm: 'ParamBrowLForm', + ParamBrowRForm: 'ParamBrowRForm', + ParamMouthForm: 'ParamMouthForm', + ParamMouthOpenY: 'ParamMouthOpenY', + ParamCheek: 'ParamCheek', + ParamBodyAngleX: 'ParamBodyAngleX', + ParamBodyAngleY: 'ParamBodyAngleY', + ParamBodyAngleZ: 'ParamBodyAngleZ', + ParamBreath: 'ParamBreath', + ParamArmLA: 'ParamArmLA', + ParamArmRA: 'ParamArmRA', + ParamArmLB: 'ParamArmLB', + ParamArmRB: 'ParamArmRB', + ParamHandL: 'ParamHandL', + ParamHandR: 'ParamHandR', + ParamHairFront: 'ParamHairFront', + ParamHairSide: 'ParamHairSide', + ParamHairBack: 'ParamHairBack', + ParamHairFluffy: 'ParamHairFluffy', + ParamShoulderY: 'ParamShoulderY', + ParamBustX: 'ParamBustX', + ParamBustY: 'ParamBustY', + ParamBaseX: 'ParamBaseX', + ParamBaseY: 'ParamBaseY', + ParamNONE: 'NONE:' +}); + +// Namespace definition for compatibility. +import * as $ from './cubismdefaultparameterid'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const HitAreaBody = $.CubismDefaultParameterId.HitAreaBody; + export const HitAreaHead = $.CubismDefaultParameterId.HitAreaHead; + export const HitAreaPrefix = $.CubismDefaultParameterId.HitAreaPrefix; + export const ParamAngleX = $.CubismDefaultParameterId.ParamAngleX; + export const ParamAngleY = $.CubismDefaultParameterId.ParamAngleY; + export const ParamAngleZ = $.CubismDefaultParameterId.ParamAngleZ; + export const ParamArmLA = $.CubismDefaultParameterId.ParamArmLA; + export const ParamArmLB = $.CubismDefaultParameterId.ParamArmLB; + export const ParamArmRA = $.CubismDefaultParameterId.ParamArmRA; + export const ParamArmRB = $.CubismDefaultParameterId.ParamArmRB; + export const ParamBaseX = $.CubismDefaultParameterId.ParamBaseX; + export const ParamBaseY = $.CubismDefaultParameterId.ParamBaseY; + export const ParamBodyAngleX = $.CubismDefaultParameterId.ParamBodyAngleX; + export const ParamBodyAngleY = $.CubismDefaultParameterId.ParamBodyAngleY; + export const ParamBodyAngleZ = $.CubismDefaultParameterId.ParamBodyAngleZ; + export const ParamBreath = $.CubismDefaultParameterId.ParamBreath; + export const ParamBrowLAngle = $.CubismDefaultParameterId.ParamBrowLAngle; + export const ParamBrowLForm = $.CubismDefaultParameterId.ParamBrowLForm; + export const ParamBrowLX = $.CubismDefaultParameterId.ParamBrowLX; + export const ParamBrowLY = $.CubismDefaultParameterId.ParamBrowLY; + export const ParamBrowRAngle = $.CubismDefaultParameterId.ParamBrowRAngle; + export const ParamBrowRForm = $.CubismDefaultParameterId.ParamBrowRForm; + export const ParamBrowRX = $.CubismDefaultParameterId.ParamBrowRX; + export const ParamBrowRY = $.CubismDefaultParameterId.ParamBrowRY; + export const ParamBustX = $.CubismDefaultParameterId.ParamBustX; + export const ParamBustY = $.CubismDefaultParameterId.ParamBustY; + export const ParamCheek = $.CubismDefaultParameterId.ParamCheek; + export const ParamEyeBallForm = $.CubismDefaultParameterId.ParamEyeBallForm; + export const ParamEyeBallX = $.CubismDefaultParameterId.ParamEyeBallX; + export const ParamEyeBallY = $.CubismDefaultParameterId.ParamEyeBallY; + export const ParamEyeLOpen = $.CubismDefaultParameterId.ParamEyeLOpen; + export const ParamEyeLSmile = $.CubismDefaultParameterId.ParamEyeLSmile; + export const ParamEyeROpen = $.CubismDefaultParameterId.ParamEyeROpen; + export const ParamEyeRSmile = $.CubismDefaultParameterId.ParamEyeRSmile; + export const ParamHairBack = $.CubismDefaultParameterId.ParamHairBack; + export const ParamHairFluffy = $.CubismDefaultParameterId.ParamHairFluffy; + export const ParamHairFront = $.CubismDefaultParameterId.ParamHairFront; + export const ParamHairSide = $.CubismDefaultParameterId.ParamHairSide; + export const ParamHandL = $.CubismDefaultParameterId.ParamHandL; + export const ParamHandR = $.CubismDefaultParameterId.ParamHandR; + export const ParamMouthForm = $.CubismDefaultParameterId.ParamMouthForm; + export const ParamMouthOpenY = $.CubismDefaultParameterId.ParamMouthOpenY; + export const ParamNONE = $.CubismDefaultParameterId.ParamNONE; + export const ParamShoulderY = $.CubismDefaultParameterId.ParamShoulderY; + export const PartsArmLPrefix = $.CubismDefaultParameterId.PartsArmLPrefix; + export const PartsArmPrefix = $.CubismDefaultParameterId.PartsArmPrefix; + export const PartsArmRPrefix = $.CubismDefaultParameterId.PartsArmRPrefix; + export const PartsIdCore = $.CubismDefaultParameterId.PartsIdCore; +} diff --git a/avatar-h5-renderer/framework/src/cubismframeworkconfig.ts b/avatar-h5-renderer/framework/src/cubismframeworkconfig.ts new file mode 100644 index 0000000..3ff7d46 --- /dev/null +++ b/avatar-h5-renderer/framework/src/cubismframeworkconfig.ts @@ -0,0 +1,32 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +//======================================================== +// ログ出力関数の設定 +//======================================================== + +//---------- ログ出力レベル 選択項目 定義 ---------- +// 詳細ログ出力設定 +export const CSM_LOG_LEVEL_VERBOSE = 0; +// デバッグログ出力設定 +export const CSM_LOG_LEVEL_DEBUG = 1; +// Infoログ出力設定 +export const CSM_LOG_LEVEL_INFO = 2; +// 警告ログ出力設定 +export const CSM_LOG_LEVEL_WARNING = 3; +// エラーログ出力設定 +export const CSM_LOG_LEVEL_ERROR = 4; +// ログ出力オフ設定 +export const CSM_LOG_LEVEL_OFF = 5; + +/** + * ログ出力レベル設定。 + * + * 強制的にログ出力レベルを変える時に定義を有効にする。 + * CSM_LOG_LEVEL_VERBOSE ~ CSM_LOG_LEVEL_OFF を選択する。 + */ +export const CSM_LOG_LEVEL: number = CSM_LOG_LEVEL_VERBOSE; diff --git a/avatar-h5-renderer/framework/src/cubismmodelsettingjson.ts b/avatar-h5-renderer/framework/src/cubismmodelsettingjson.ts new file mode 100644 index 0000000..a494f3a --- /dev/null +++ b/avatar-h5-renderer/framework/src/cubismmodelsettingjson.ts @@ -0,0 +1,790 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismModelSetting } from './icubismmodelsetting'; +import { CubismIdHandle } from './id/cubismid'; +import { CubismFramework } from './live2dcubismframework'; +import { CubismJson, Value } from './utils/cubismjson'; + +export enum FrequestNode { + FrequestNode_Groups, // getRoot().getValueByString(Groups) + FrequestNode_Moc, // getRoot().getValueByString(FileReferences).getValueByString(Moc) + FrequestNode_Motions, // getRoot().getValueByString(FileReferences).getValueByString(Motions) + FrequestNode_Expressions, // getRoot().getValueByString(FileReferences).getValueByString(Expressions) + FrequestNode_Textures, // getRoot().getValueByString(FileReferences).getValueByString(Textures) + FrequestNode_Physics, // getRoot().getValueByString(FileReferences).getValueByString(Physics) + FrequestNode_Pose, // getRoot().getValueByString(FileReferences).getValueByString(Pose) + FrequestNode_HitAreas // getRoot().getValueByString(HitAreas) +} + +/** + * Model3Jsonパーサー + * + * model3.jsonファイルをパースして値を取得する + */ +export class CubismModelSettingJson extends ICubismModelSetting { + /** + * 引数付きコンストラクタ + * + * @param buffer Model3Jsonをバイト配列として読み込んだデータバッファ + * @param size Model3Jsonのデータサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + super(); + this._json = CubismJson.create(buffer, size); + + if (this.getJson()) { + this._jsonValue = [ + // 順番はenum FrequestNodeと一致させる + this.getJson().getRoot().getValueByString(this.groups), + this.getJson() + .getRoot() + .getValueByString(this.fileReferences) + .getValueByString(this.moc), + this.getJson() + .getRoot() + .getValueByString(this.fileReferences) + .getValueByString(this.motions), + this.getJson() + .getRoot() + .getValueByString(this.fileReferences) + .getValueByString(this.expressions), + this.getJson() + .getRoot() + .getValueByString(this.fileReferences) + .getValueByString(this.textures), + this.getJson() + .getRoot() + .getValueByString(this.fileReferences) + .getValueByString(this.physics), + this.getJson() + .getRoot() + .getValueByString(this.fileReferences) + .getValueByString(this.pose), + this.getJson().getRoot().getValueByString(this.hitAreas) + ]; + } + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + + this._jsonValue = null; + } + + /** + * CubismJsonオブジェクトを取得する + * + * @return CubismJson + */ + public getJson(): CubismJson { + return this._json; + } + + /** + * Mocファイルの名前を取得する + * @return Mocファイルの名前 + */ + public getModelFileName(): string { + if (!this.isExistModelFile()) { + return ''; + } + return this._jsonValue[FrequestNode.FrequestNode_Moc].getRawString(); + } + + /** + * モデルが使用するテクスチャの数を取得する + * テクスチャの数 + */ + public getTextureCount(): number { + if (!this.isExistTextureFiles()) { + return 0; + } + + return this._jsonValue[FrequestNode.FrequestNode_Textures].getSize(); + } + + /** + * テクスチャが配置されたディレクトリの名前を取得する + * @return テクスチャが配置されたディレクトリの名前 + */ + public getTextureDirectory(): string { + const texturePath = this._jsonValue[FrequestNode.FrequestNode_Textures] + .getValueByIndex(0) + .getRawString(); + + const pathArray = texturePath.split('/'); + // 最後の要素はテクスチャ名なので不要 + const arrayLength = pathArray.length - 1; + let textureDirectoryStr = ''; + + // 分割したパスを結合 + for (let i = 0; i < arrayLength; i++) { + textureDirectoryStr += pathArray[i]; + if (i < arrayLength - 1) { + textureDirectoryStr += '/'; + } + } + + return textureDirectoryStr; + } + + /** + * モデルが使用するテクスチャの名前を取得する + * @param index 配列のインデックス値 + * @return テクスチャの名前 + */ + public getTextureFileName(index: number): string { + return this._jsonValue[FrequestNode.FrequestNode_Textures] + .getValueByIndex(index) + .getRawString(); + } + + /** + * モデルに設定された当たり判定の数を取得する + * @return モデルに設定された当たり判定の数 + */ + public getHitAreasCount(): number { + if (!this.isExistHitAreas()) { + return 0; + } + + return this._jsonValue[FrequestNode.FrequestNode_HitAreas].getSize(); + } + + /** + * 当たり判定に設定されたIDを取得する + * + * @param index 配列のindex + * @return 当たり判定に設定されたID + */ + public getHitAreaId(index: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._jsonValue[FrequestNode.FrequestNode_HitAreas] + .getValueByIndex(index) + .getValueByString(this.id) + .getRawString() + ); + } + + /** + * 当たり判定に設定された名前を取得する + * @param index 配列のインデックス値 + * @return 当たり判定に設定された名前 + */ + public getHitAreaName(index: number): string { + return this._jsonValue[FrequestNode.FrequestNode_HitAreas] + .getValueByIndex(index) + .getValueByString(this.name) + .getRawString(); + } + + /** + * 物理演算設定ファイルの名前を取得する + * @return 物理演算設定ファイルの名前 + */ + public getPhysicsFileName(): string { + if (!this.isExistPhysicsFile()) { + return ''; + } + + return this._jsonValue[FrequestNode.FrequestNode_Physics].getRawString(); + } + + /** + * パーツ切り替え設定ファイルの名前を取得する + * @return パーツ切り替え設定ファイルの名前 + */ + public getPoseFileName(): string { + if (!this.isExistPoseFile()) { + return ''; + } + + return this._jsonValue[FrequestNode.FrequestNode_Pose].getRawString(); + } + + /** + * 表情設定ファイルの数を取得する + * @return 表情設定ファイルの数 + */ + public getExpressionCount(): number { + if (!this.isExistExpressionFile()) { + return 0; + } + + return this._jsonValue[FrequestNode.FrequestNode_Expressions].getSize(); + } + + /** + * 表情設定ファイルを識別する名前(別名)を取得する + * @param index 配列のインデックス値 + * @return 表情の名前 + */ + public getExpressionName(index: number): string { + return this._jsonValue[FrequestNode.FrequestNode_Expressions] + .getValueByIndex(index) + .getValueByString(this.name) + .getRawString(); + } + + /** + * 表情設定ファイルの名前を取得する + * @param index 配列のインデックス値 + * @return 表情設定ファイルの名前 + */ + public getExpressionFileName(index: number): string { + return this._jsonValue[FrequestNode.FrequestNode_Expressions] + .getValueByIndex(index) + .getValueByString(this.filePath) + .getRawString(); + } + + /** + * モーショングループの数を取得する + * @return モーショングループの数 + */ + public getMotionGroupCount(): number { + if (!this.isExistMotionGroups()) { + return 0; + } + + return this._jsonValue[FrequestNode.FrequestNode_Motions].getKeys().length; + } + + /** + * モーショングループの名前を取得する + * @param index 配列のインデックス値 + * @return モーショングループの名前 + */ + public getMotionGroupName(index: number): string { + if (!this.isExistMotionGroups()) { + return null; + } + + return this._jsonValue[FrequestNode.FrequestNode_Motions].getKeys()[index]; + } + + /** + * モーショングループに含まれるモーションの数を取得する + * @param groupName モーショングループの名前 + * @return モーショングループの数 + */ + public getMotionCount(groupName: string): number { + if (!this.isExistMotionGroupName(groupName)) { + return 0; + } + + return this._jsonValue[FrequestNode.FrequestNode_Motions] + .getValueByString(groupName) + .getSize(); + } + + /** + * グループ名とインデックス値からモーションファイル名を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return モーションファイルの名前 + */ + public getMotionFileName(groupName: string, index: number): string { + if (!this.isExistMotionGroupName(groupName)) { + return ''; + } + + return this._jsonValue[FrequestNode.FrequestNode_Motions] + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(this.filePath) + .getRawString(); + } + + /** + * モーションに対応するサウンドファイルの名前を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return サウンドファイルの名前 + */ + public getMotionSoundFileName(groupName: string, index: number): string { + if (!this.isExistMotionSoundFile(groupName, index)) { + return ''; + } + + return this._jsonValue[FrequestNode.FrequestNode_Motions] + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(this.soundPath) + .getRawString(); + } + + /** + * モーション開始時のフェードイン処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードイン処理時間[秒] + */ + public getMotionFadeInTimeValue(groupName: string, index: number): number { + if (!this.isExistMotionFadeIn(groupName, index)) { + return -1.0; + } + + return this._jsonValue[FrequestNode.FrequestNode_Motions] + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(this.fadeInTime) + .toFloat(); + } + + /** + * モーション終了時のフェードアウト処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードアウト処理時間[秒] + */ + public getMotionFadeOutTimeValue(groupName: string, index: number): number { + if (!this.isExistMotionFadeOut(groupName, index)) { + return -1.0; + } + + return this._jsonValue[FrequestNode.FrequestNode_Motions] + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(this.fadeOutTime) + .toFloat(); + } + + /** + * ユーザーデータのファイル名を取得する + * @return ユーザーデータのファイル名 + */ + public getUserDataFile(): string { + if (!this.isExistUserDataFile()) { + return ''; + } + + return this.getJson() + .getRoot() + .getValueByString(this.fileReferences) + .getValueByString(this.userData) + .getRawString(); + } + + /** + * レイアウト情報を取得する + * @param outLayoutMap Mapクラスのインスタンス + * @return true レイアウト情報が存在する + * @return false レイアウト情報が存在しない + */ + public getLayoutMap(outLayoutMap: Map): boolean { + // 存在しない要素にアクセスするとエラーになるためValueがnullの場合はnullを代入する + const map: Map = this.getJson() + .getRoot() + .getValueByString(this.layout) + .getMap(); + + if (map == null) { + return false; + } + + let ret = false; + + for (const element of map) { + outLayoutMap.set(element[0], element[1].toFloat()); + ret = true; + } + + return ret; + } + + /** + * 目パチに関連付けられたパラメータの数を取得する + * @return 目パチに関連付けられたパラメータの数 + */ + public getEyeBlinkParameterCount(): number { + if (!this.isExistEyeBlinkParameters()) { + return 0; + } + + let num = 0; + for ( + let i = 0; + i < this._jsonValue[FrequestNode.FrequestNode_Groups].getSize(); + i++ + ) { + const refI: Value = + this._jsonValue[FrequestNode.FrequestNode_Groups].getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(this.name).getRawString() == this.eyeBlink) { + num = refI.getValueByString(this.ids).getVector().length; + break; + } + } + + return num; + } + + /** + * 目パチに関連付けられたパラメータのIDを取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public getEyeBlinkParameterId(index: number): CubismIdHandle { + if (!this.isExistEyeBlinkParameters()) { + return null; + } + + for ( + let i = 0; + i < this._jsonValue[FrequestNode.FrequestNode_Groups].getSize(); + i++ + ) { + const refI: Value = + this._jsonValue[FrequestNode.FrequestNode_Groups].getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(this.name).getRawString() == this.eyeBlink) { + return CubismFramework.getIdManager().getId( + refI.getValueByString(this.ids).getValueByIndex(index).getRawString() + ); + } + } + return null; + } + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @return リップシンクに関連付けられたパラメータの数 + */ + public getLipSyncParameterCount(): number { + if (!this.isExistLipSyncParameters()) { + return 0; + } + + let num = 0; + for ( + let i = 0; + i < this._jsonValue[FrequestNode.FrequestNode_Groups].getSize(); + i++ + ) { + const refI: Value = + this._jsonValue[FrequestNode.FrequestNode_Groups].getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(this.name).getRawString() == this.lipSync) { + num = refI.getValueByString(this.ids).getVector().length; + break; + } + } + + return num; + } + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public getLipSyncParameterId(index: number): CubismIdHandle { + if (!this.isExistLipSyncParameters()) { + return null; + } + + for ( + let i = 0; + i < this._jsonValue[FrequestNode.FrequestNode_Groups].getSize(); + i++ + ) { + const refI: Value = + this._jsonValue[FrequestNode.FrequestNode_Groups].getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(this.name).getRawString() == this.lipSync) { + return CubismFramework.getIdManager().getId( + refI.getValueByString(this.ids).getValueByIndex(index).getRawString() + ); + } + } + return null; + } + + /** + * モデルファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistModelFile(): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Moc]; + return !node.isNull() && !node.isError(); + } + + /** + * テクスチャファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistTextureFiles(): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Textures]; + return !node.isNull() && !node.isError(); + } + + /** + * 当たり判定のキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistHitAreas(): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_HitAreas]; + return !node.isNull() && !node.isError(); + } + + /** + * 物理演算ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistPhysicsFile(): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Physics]; + return !node.isNull() && !node.isError(); + } + + /** + * ポーズ設定ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistPoseFile(): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Pose]; + return !node.isNull() && !node.isError(); + } + + /** + * 表情設定ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistExpressionFile(): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Expressions]; + return !node.isNull() && !node.isError(); + } + + /** + * モーショングループのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistMotionGroups(): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Motions]; + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーショングループのキーが存在するかどうかを確認する + * @param groupName グループ名 + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistMotionGroupName(groupName: string): boolean { + const node: Value = + this._jsonValue[FrequestNode.FrequestNode_Motions].getValueByString( + groupName + ); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するサウンドファイルのキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistMotionSoundFile(groupName: string, index: number): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Motions] + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(this.soundPath); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するフェードイン時間のキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistMotionFadeIn(groupName: string, index: number): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Motions] + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(this.fadeInTime); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するフェードアウト時間のキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistMotionFadeOut(groupName: string, index: number): boolean { + const node: Value = this._jsonValue[FrequestNode.FrequestNode_Motions] + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(this.fadeOutTime); + return !node.isNull() && !node.isError(); + } + + /** + * UserDataのファイル名が存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistUserDataFile(): boolean { + const node: Value = this.getJson() + .getRoot() + .getValueByString(this.fileReferences) + .getValueByString(this.userData); + return !node.isNull() && !node.isError(); + } + + /** + * 目ぱちに対応付けられたパラメータが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistEyeBlinkParameters(): boolean { + if ( + this._jsonValue[FrequestNode.FrequestNode_Groups].isNull() || + this._jsonValue[FrequestNode.FrequestNode_Groups].isError() + ) { + return false; + } + + for ( + let i = 0; + i < this._jsonValue[FrequestNode.FrequestNode_Groups].getSize(); + ++i + ) { + if ( + this._jsonValue[FrequestNode.FrequestNode_Groups] + .getValueByIndex(i) + .getValueByString(this.name) + .getRawString() == this.eyeBlink + ) { + return true; + } + } + + return false; + } + + /** + * リップシンクに対応付けられたパラメータが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + protected isExistLipSyncParameters(): boolean { + if ( + this._jsonValue[FrequestNode.FrequestNode_Groups].isNull() || + this._jsonValue[FrequestNode.FrequestNode_Groups].isError() + ) { + return false; + } + for ( + let i = 0; + i < this._jsonValue[FrequestNode.FrequestNode_Groups].getSize(); + ++i + ) { + if ( + this._jsonValue[FrequestNode.FrequestNode_Groups] + .getValueByIndex(i) + .getValueByString(this.name) + .getRawString() == this.lipSync + ) { + return true; + } + } + return false; + } + + protected _json: CubismJson; + protected _jsonValue: Array; + + /** + * Model3Jsonのキー文字列 + */ + protected readonly version = 'Version'; + protected readonly fileReferences = 'FileReferences'; + + protected readonly groups = 'Groups'; + protected readonly layout = 'Layout'; + protected readonly hitAreas = 'HitAreas'; + + protected readonly moc = 'Moc'; + protected readonly textures = 'Textures'; + protected readonly physics = 'Physics'; + protected readonly pose = 'Pose'; + protected readonly expressions = 'Expressions'; + protected readonly motions = 'Motions'; + + protected readonly userData = 'UserData'; + protected readonly name = 'Name'; + protected readonly filePath = 'File'; + protected readonly id = 'Id'; + protected readonly ids = 'Ids'; + protected readonly target = 'Target'; + + // Motions + protected readonly idle = 'Idle'; + protected readonly tapBody = 'TapBody'; + protected readonly pinchIn = 'PinchIn'; + protected readonly pinchOut = 'PinchOut'; + protected readonly shake = 'Shake'; + protected readonly flickHead = 'FlickHead'; + protected readonly parameter = 'Parameter'; + + protected readonly soundPath = 'Sound'; + protected readonly fadeInTime = 'FadeInTime'; + protected readonly fadeOutTime = 'FadeOutTime'; + + // Layout + protected readonly centerX = 'CenterX'; + protected readonly centerY = 'CenterY'; + protected readonly x = 'X'; + protected readonly y = 'Y'; + protected readonly width = 'Width'; + protected readonly height = 'Height'; + + protected readonly lipSync = 'LipSync'; + protected readonly eyeBlink = 'EyeBlink'; + + protected readonly initParameter = 'init_param'; + protected readonly initPartsVisible = 'init_parts_visible'; + protected readonly val = 'val'; +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodelsettingjson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModelSettingJson = $.CubismModelSettingJson; + export type CubismModelSettingJson = $.CubismModelSettingJson; + export const FrequestNode = $.FrequestNode; + export type FrequestNode = $.FrequestNode; +} diff --git a/avatar-h5-renderer/framework/src/effect/cubismbreath.ts b/avatar-h5-renderer/framework/src/effect/cubismbreath.ts new file mode 100644 index 0000000..2e5f4d0 --- /dev/null +++ b/avatar-h5-renderer/framework/src/effect/cubismbreath.ts @@ -0,0 +1,123 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismModel } from '../model/cubismmodel'; + +/** + * 呼吸機能 + * + * 呼吸機能を提供する。 + */ +export class CubismBreath { + /** + * インスタンスの作成 + */ + public static create(): CubismBreath { + return new CubismBreath(); + } + + /** + * インスタンスの破棄 + * @param instance 対象のCubismBreath + */ + public static delete(instance: CubismBreath): void { + if (instance != null) { + instance = null; + } + } + + /** + * 呼吸のパラメータの紐づけ + * @param breathParameters 呼吸を紐づけたいパラメータのリスト + */ + public setParameters(breathParameters: Array): void { + this._breathParameters = breathParameters; + } + + /** + * 呼吸に紐づいているパラメータの取得 + * @return 呼吸に紐づいているパラメータのリスト + */ + public getParameters(): Array { + return this._breathParameters; + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters(model: CubismModel, deltaTimeSeconds: number): void { + this._currentTime += deltaTimeSeconds; + + const t: number = this._currentTime * 2.0 * Math.PI; + + for (let i = 0; i < this._breathParameters.length; ++i) { + const data: BreathParameterData = this._breathParameters[i]; + + model.addParameterValueById( + data.parameterId, + data.offset + data.peak * Math.sin(t / data.cycle), + data.weight + ); + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._currentTime = 0.0; + } + + _breathParameters: Array; // 呼吸にひもづいているパラメータのリスト + _currentTime: number; // 積算時間[秒] +} + +/** + * 呼吸のパラメータ情報 + */ +export class BreathParameterData { + /** + * コンストラクタ + * @param parameterId 呼吸をひもづけるパラメータID + * @param offset 呼吸を正弦波としたときの、波のオフセット + * @param peak 呼吸を正弦波としたときの、波の高さ + * @param cycle 呼吸を正弦波としたときの、波の周期 + * @param weight パラメータへの重み + */ + constructor( + parameterId?: CubismIdHandle, + offset?: number, + peak?: number, + cycle?: number, + weight?: number + ) { + this.parameterId = parameterId == undefined ? null : parameterId; + this.offset = offset == undefined ? 0.0 : offset; + this.peak = peak == undefined ? 0.0 : peak; + this.cycle = cycle == undefined ? 0.0 : cycle; + this.weight = weight == undefined ? 0.0 : weight; + } + + parameterId: CubismIdHandle; // 呼吸をひもづけるパラメータID\ + offset: number; // 呼吸を正弦波としたときの、波のオフセット + peak: number; // 呼吸を正弦波としたときの、波の高さ + cycle: number; // 呼吸を正弦波としたときの、波の周期 + weight: number; // パラメータへの重み +} + +// Namespace definition for compatibility. +import * as $ from './cubismbreath'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const BreathParameterData = $.BreathParameterData; + export type BreathParameterData = $.BreathParameterData; + export const CubismBreath = $.CubismBreath; + export type CubismBreath = $.CubismBreath; +} diff --git a/avatar-h5-renderer/framework/src/effect/cubismeyeblink.ts b/avatar-h5-renderer/framework/src/effect/cubismeyeblink.ts new file mode 100644 index 0000000..3afebce --- /dev/null +++ b/avatar-h5-renderer/framework/src/effect/cubismeyeblink.ts @@ -0,0 +1,234 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismModelSetting } from '../icubismmodelsetting'; +import { CubismIdHandle } from '../id/cubismid'; +import { CubismModel } from '../model/cubismmodel'; + +/** + * 自動まばたき機能 + * + * 自動まばたき機能を提供する。 + */ +export class CubismEyeBlink { + /** + * インスタンスを作成する + * @param modelSetting モデルの設定情報 + * @return 作成されたインスタンス + * @note 引数がNULLの場合、パラメータIDが設定されていない空のインスタンスを作成する。 + */ + public static create( + modelSetting: ICubismModelSetting = null + ): CubismEyeBlink { + return new CubismEyeBlink(modelSetting); + } + + /** + * インスタンスの破棄 + * @param eyeBlink 対象のCubismEyeBlink + */ + public static delete(eyeBlink: CubismEyeBlink): void { + if (eyeBlink != null) { + eyeBlink = null; + } + } + + /** + * まばたきの間隔の設定 + * @param blinkingInterval まばたきの間隔の時間[秒] + */ + public setBlinkingInterval(blinkingInterval: number): void { + this._blinkingIntervalSeconds = blinkingInterval; + } + + /** + * まばたきのモーションの詳細設定 + * @param closing まぶたを閉じる動作の所要時間[秒] + * @param closed まぶたを閉じている動作の所要時間[秒] + * @param opening まぶたを開く動作の所要時間[秒] + */ + public setBlinkingSetting( + closing: number, + closed: number, + opening: number + ): void { + this._closingSeconds = closing; + this._closedSeconds = closed; + this._openingSeconds = opening; + } + + /** + * まばたきさせるパラメータIDのリストの設定 + * @param parameterIds パラメータのIDのリスト + */ + public setParameterIds(parameterIds: Array): void { + this._parameterIds = parameterIds; + } + + /** + * まばたきさせるパラメータIDのリストの取得 + * @return パラメータIDのリスト + */ + public getParameterIds(): Array { + return this._parameterIds; + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters(model: CubismModel, deltaTimeSeconds: number): void { + this._userTimeSeconds += deltaTimeSeconds; + let parameterValue: number; + let t = 0.0; + const blinkingState: EyeState = this._blinkingState; + + switch (blinkingState) { + case EyeState.EyeState_Closing: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._closingSeconds; + + if (t >= 1.0) { + t = 1.0; + this._blinkingState = EyeState.EyeState_Closed; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 1.0 - t; + + break; + case EyeState.EyeState_Closed: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._closedSeconds; + + if (t >= 1.0) { + this._blinkingState = EyeState.EyeState_Opening; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 0.0; + + break; + case EyeState.EyeState_Opening: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._openingSeconds; + + if (t >= 1.0) { + t = 1.0; + this._blinkingState = EyeState.EyeState_Interval; + this._nextBlinkingTime = this.determinNextBlinkingTiming(); + } + + parameterValue = t; + + break; + case EyeState.EyeState_Interval: + if (this._nextBlinkingTime < this._userTimeSeconds) { + this._blinkingState = EyeState.EyeState_Closing; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 1.0; + + break; + case EyeState.EyeState_First: + default: + this._blinkingState = EyeState.EyeState_Interval; + this._nextBlinkingTime = this.determinNextBlinkingTiming(); + + parameterValue = 1.0; + break; + } + + if (!CubismEyeBlink.CloseIfZero) { + parameterValue = -parameterValue; + } + + for (let i = 0; i < this._parameterIds.length; ++i) { + model.setParameterValueById(this._parameterIds[i], parameterValue); + } + } + + /** + * コンストラクタ + * @param modelSetting モデルの設定情報 + */ + public constructor(modelSetting: ICubismModelSetting) { + this._blinkingState = EyeState.EyeState_First; + this._nextBlinkingTime = 0.0; + this._stateStartTimeSeconds = 0.0; + this._blinkingIntervalSeconds = 4.0; + this._closingSeconds = 0.1; + this._closedSeconds = 0.05; + this._openingSeconds = 0.15; + this._userTimeSeconds = 0.0; + this._parameterIds = new Array(); + + if (modelSetting == null) { + return; + } + + this._parameterIds.length = modelSetting.getEyeBlinkParameterCount(); + for (let i = 0; i < modelSetting.getEyeBlinkParameterCount(); ++i) { + this._parameterIds[i] = modelSetting.getEyeBlinkParameterId(i); + } + } + + /** + * 次の瞬きのタイミングの決定 + * + * @return 次のまばたきを行う時刻[秒] + */ + public determinNextBlinkingTiming(): number { + const r: number = Math.random(); + return ( + this._userTimeSeconds + r * (2.0 * this._blinkingIntervalSeconds - 1.0) + ); + } + + _blinkingState: number; // 現在の状態 + _parameterIds: Array; // 操作対象のパラメータのIDのリスト + _nextBlinkingTime: number; // 次のまばたきの時刻[秒] + _stateStartTimeSeconds: number; // 現在の状態が開始した時刻[秒] + _blinkingIntervalSeconds: number; // まばたきの間隔[秒] + _closingSeconds: number; // まぶたを閉じる動作の所要時間[秒] + _closedSeconds: number; // まぶたを閉じている動作の所要時間[秒] + _openingSeconds: number; // まぶたを開く動作の所要時間[秒] + _userTimeSeconds: number; // デルタ時間の積算値[秒] + + /** + * IDで指定された目のパラメータが、0のときに閉じるなら true 、1の時に閉じるなら false 。 + */ + static readonly CloseIfZero: boolean = true; +} + +/** + * まばたきの状態 + * + * まばたきの状態を表す列挙型 + */ +export enum EyeState { + EyeState_First = 0, // 初期状態 + EyeState_Interval, // まばたきしていない状態 + EyeState_Closing, // まぶたが閉じていく途中の状態 + EyeState_Closed, // まぶたが閉じている状態 + EyeState_Opening // まぶたが開いていく途中の状態 +} + +// Namespace definition for compatibility. +import * as $ from './cubismeyeblink'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismEyeBlink = $.CubismEyeBlink; + export type CubismEyeBlink = $.CubismEyeBlink; + export const EyeState = $.EyeState; + export type EyeState = $.EyeState; +} diff --git a/avatar-h5-renderer/framework/src/effect/cubismlook.ts b/avatar-h5-renderer/framework/src/effect/cubismlook.ts new file mode 100644 index 0000000..95d94a3 --- /dev/null +++ b/avatar-h5-renderer/framework/src/effect/cubismlook.ts @@ -0,0 +1,120 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismModel } from '../model/cubismmodel'; + +/** + * ターゲットによるパラメータ追従機能 + * + * ドラッグ入力に対するパラメータ追従機能を提供する。 + */ +export class CubismLook { + /** + * インスタンスの作成 + */ + public static create(): CubismLook { + return new CubismLook(); + } + + /** + * インスタンスの破棄 + * @param instance 対象のCubismDrag + */ + public static delete(instance: CubismLook): void { + if (instance != null) { + instance = null; + } + } + + /** + * ターゲット追従のパラメータの紐づけ + * @param lookParameters ターゲット追従を紐づけたいパラメータのリスト + */ + public setParameters(lookParameters: Array): void { + this._lookParameters = lookParameters; + } + + /** + * ターゲット追従に紐づいているパラメータの取得 + * @return ターゲット追従に紐づいているパラメータのリスト + */ + public getParameters(): Array { + return this._lookParameters; + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param dragX ターゲットのX座標 + * @param dragY ターゲットのY座標 + */ + public updateParameters( + model: CubismModel, + dragX: number, + dragY: number + ): void { + for (let i = 0; i < this._lookParameters.length; ++i) { + const data: LookParameterData = this._lookParameters[i]; + + model.addParameterValueById( + data.parameterId, + data.factorX * dragX + + data.factorY * dragY + + data.factorXY * dragX * dragY + ); + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._lookParameters = new Array(); + } + + _lookParameters: Array; // ターゲット追従に紐づいているパラメータのリスト +} + +/** + * ターゲット追従のパラメータ情報 + */ +export class LookParameterData { + /** + * コンストラクタ + * @param parameterId ターゲット追従を紐づけるパラメータID + * @param factorX X方向ドラッグ入力に対する係数 + * @param factorY Y方向ドラッグ入力に対する係数 + * @param factorXY XY積ドラッグ入力に対する係数 + */ + constructor( + parameterId?: CubismIdHandle, + factorX?: number, + factorY?: number, + factorXY?: number + ) { + this.parameterId = parameterId == undefined ? null : parameterId; + this.factorX = factorX == undefined ? 0.0 : factorX; + this.factorY = factorY == undefined ? 0.0 : factorY; + this.factorXY = factorXY == undefined ? 0.0 : factorXY; + } + + parameterId: CubismIdHandle; // ターゲット追従を紐づけるパラメータID + factorX: number; // X方向ドラッグ入力に対する係数 + factorY: number; // Y方向ドラッグ入力に対する係数 + factorXY: number; // XY積ドラッグ入力に対する係数 +} + +// Namespace definition for compatibility. +import * as $ from './cubismlook'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const LookParameterData = $.LookParameterData; + export type LookParameterData = $.LookParameterData; + export const CubismLook = $.CubismLook; + export type CubismLook = $.CubismLook; +} diff --git a/avatar-h5-renderer/framework/src/effect/cubismpose.ts b/avatar-h5-renderer/framework/src/effect/cubismpose.ts new file mode 100644 index 0000000..cbe5ce3 --- /dev/null +++ b/avatar-h5-renderer/framework/src/effect/cubismpose.ts @@ -0,0 +1,396 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismJson, Value } from '../utils/cubismjson'; + +const Epsilon = 0.001; +const DefaultFadeInSeconds = 0.5; + +// Pose.jsonのタグ +const FadeIn = 'FadeInTime'; +const Link = 'Link'; +const Groups = 'Groups'; +const Id = 'Id'; + +/** + * パーツの不透明度の設定 + * + * パーツの不透明度の管理と設定を行う。 + */ +export class CubismPose { + /** + * インスタンスの作成 + * @param pose3json pose3.jsonのデータ + * @param size pose3.jsonのデータのサイズ[byte] + * @return 作成されたインスタンス + */ + public static create(pose3json: ArrayBuffer, size: number): CubismPose { + const json: CubismJson = CubismJson.create(pose3json, size); + if (!json) { + return null; + } + + const ret: CubismPose = new CubismPose(); + const root: Value = json.getRoot(); + + // フェード時間の指定 + if (!root.getValueByString(FadeIn).isNull()) { + ret._fadeTimeSeconds = root + .getValueByString(FadeIn) + .toFloat(DefaultFadeInSeconds); + + if (ret._fadeTimeSeconds < 0.0) { + ret._fadeTimeSeconds = DefaultFadeInSeconds; + } + } + + // パーツグループ + const poseListInfo: Value = root.getValueByString(Groups); + const poseCount: number = poseListInfo.getSize(); + + ret._partGroupCounts.length = poseCount; + for (let poseIndex = 0; poseIndex < poseCount; ++poseIndex) { + const idListInfo: Value = poseListInfo.getValueByIndex(poseIndex); + const idCount: number = idListInfo.getSize(); + let groupCount = 0; + + for (let groupIndex = 0; groupIndex < idCount; ++groupIndex) { + const partInfo: Value = idListInfo.getValueByIndex(groupIndex); + const partData: PartData = new PartData(); + const parameterId: CubismIdHandle = + CubismFramework.getIdManager().getId( + partInfo.getValueByString(Id).getRawString() + ); + + partData.partId = parameterId; + + // リンクするパーツの設定 + if (!partInfo.getValueByString(Link).isNull()) { + const linkListInfo: Value = partInfo.getValueByString(Link); + const linkCount: number = linkListInfo.getSize(); + + for (let linkIndex = 0; linkIndex < linkCount; ++linkIndex) { + const linkPart: PartData = new PartData(); + const linkId: CubismIdHandle = CubismFramework.getIdManager().getId( + linkListInfo.getValueByIndex(linkIndex).getString() + ); + + linkPart.partId = linkId; + + partData.link.push(linkPart); + } + } + + ret._partGroups.push(partData.clone()); + + ++groupCount; + } + + ret._partGroupCounts[poseIndex] = groupCount; + } + + CubismJson.delete(json); + + return ret; + } + + /** + * インスタンスを破棄する + * @param pose 対象のCubismPose + */ + public static delete(pose: CubismPose): void { + if (pose != null) { + pose = null; + } + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters(model: CubismModel, deltaTimeSeconds: number): void { + // 前回のモデルと同じでない場合は初期化が必要 + if (model != this._lastModel) { + // パラメータインデックスの初期化 + this.reset(model); + } + + this._lastModel = model; + + // 設定から時間を変更すると、経過時間がマイナスになる事があるので、経過時間0として対応 + if (deltaTimeSeconds < 0.0) { + deltaTimeSeconds = 0.0; + } + + let beginIndex = 0; + + for (let i = 0; i < this._partGroupCounts.length; i++) { + const partGroupCount: number = this._partGroupCounts[i]; + + this.doFade(model, deltaTimeSeconds, beginIndex, partGroupCount); + + beginIndex += partGroupCount; + } + + this.copyPartOpacities(model); + } + + /** + * 表示を初期化 + * @param model 対象のモデル + * @note 不透明度の初期値が0でないパラメータは、不透明度を1に設定する + */ + public reset(model: CubismModel): void { + let beginIndex = 0; + + for (let i = 0; i < this._partGroupCounts.length; ++i) { + const groupCount: number = this._partGroupCounts[i]; + + for (let j: number = beginIndex; j < beginIndex + groupCount; ++j) { + this._partGroups[j].initialize(model); + + const partsIndex: number = this._partGroups[j].partIndex; + const paramIndex: number = this._partGroups[j].parameterIndex; + + if (partsIndex < 0) { + continue; + } + + model.setPartOpacityByIndex(partsIndex, j == beginIndex ? 1.0 : 0.0); + model.setParameterValueByIndex(paramIndex, j == beginIndex ? 1.0 : 0.0); + + for (let k = 0; k < this._partGroups[j].link.length; ++k) { + this._partGroups[j].link[k].initialize(model); + } + } + + beginIndex += groupCount; + } + } + + /** + * パーツの不透明度をコピー + * + * @param model 対象のモデル + */ + public copyPartOpacities(model: CubismModel): void { + for ( + let groupIndex = 0; + groupIndex < this._partGroups.length; + ++groupIndex + ) { + const partData: PartData = this._partGroups[groupIndex]; + + if (partData.link.length == 0) { + continue; // 連動するパラメータはない + } + + const partIndex: number = this._partGroups[groupIndex].partIndex; + const opacity: number = model.getPartOpacityByIndex(partIndex); + + for (let linkIndex = 0; linkIndex < partData.link.length; ++linkIndex) { + const linkPart: PartData = partData.link[linkIndex]; + const linkPartIndex: number = linkPart.partIndex; + + if (linkPartIndex < 0) { + continue; + } + + model.setPartOpacityByIndex(linkPartIndex, opacity); + } + } + } + + /** + * パーツのフェード操作を行う。 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + * @param beginIndex フェード操作を行うパーツグループの先頭インデックス + * @param partGroupCount フェード操作を行うパーツグループの個数 + */ + public doFade( + model: CubismModel, + deltaTimeSeconds: number, + beginIndex: number, + partGroupCount: number + ): void { + let visiblePartIndex = -1; + let newOpacity = 1.0; + + const phi = 0.5; + const backOpacityThreshold = 0.15; + + // 現在、表示状態になっているパーツを取得 + for (let i: number = beginIndex; i < beginIndex + partGroupCount; ++i) { + const partIndex: number = this._partGroups[i].partIndex; + const paramIndex: number = this._partGroups[i].parameterIndex; + + if (model.getParameterValueByIndex(paramIndex) > Epsilon) { + if (visiblePartIndex >= 0) { + break; + } + + visiblePartIndex = i; + // ゼロ除算の回避 + if (this._fadeTimeSeconds == 0) { + newOpacity = 1.0; + continue; + } + + newOpacity = model.getPartOpacityByIndex(partIndex); + + // 新しい不透明度を計算 + newOpacity += deltaTimeSeconds / this._fadeTimeSeconds; + + if (newOpacity > 1.0) { + newOpacity = 1.0; + } + } + } + + if (visiblePartIndex < 0) { + visiblePartIndex = 0; + newOpacity = 1.0; + } + + // 表示パーツ、非表示パーツの不透明度を設定する + for (let i: number = beginIndex; i < beginIndex + partGroupCount; ++i) { + const partsIndex: number = this._partGroups[i].partIndex; + + // 表示パーツの設定 + if (visiblePartIndex == i) { + model.setPartOpacityByIndex(partsIndex, newOpacity); // 先に設定 + } + // 非表示パーツの設定 + else { + let opacity: number = model.getPartOpacityByIndex(partsIndex); + let a1: number; // 計算によって求められる不透明度 + + if (newOpacity < phi) { + a1 = (newOpacity * (phi - 1)) / phi + 1.0; // (0,1),(phi,phi)を通る直線式 + } else { + a1 = ((1 - newOpacity) * phi) / (1.0 - phi); // (1,0),(phi,phi)を通る直線式 + } + + // 背景の見える割合を制限する場合 + const backOpacity: number = (1.0 - a1) * (1.0 - newOpacity); + + if (backOpacity > backOpacityThreshold) { + a1 = 1.0 - backOpacityThreshold / (1.0 - newOpacity); + } + + if (opacity > a1) { + opacity = a1; // 計算の不透明度よりも大きければ(濃ければ)不透明度を上げる + } + + model.setPartOpacityByIndex(partsIndex, opacity); + } + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._fadeTimeSeconds = DefaultFadeInSeconds; + this._lastModel = null; + this._partGroups = new Array(); + this._partGroupCounts = new Array(); + } + + _partGroups: Array; // パーツグループ + _partGroupCounts: Array; // それぞれのパーツグループの個数 + _fadeTimeSeconds: number; // フェード時間[秒] + _lastModel: CubismModel; // 前回操作したモデル +} + +/** + * パーツにまつわるデータを管理 + */ +export class PartData { + /** + * コンストラクタ + */ + constructor(v?: PartData) { + this.parameterIndex = 0; + this.partIndex = 0; + this.link = new Array(); + + if (v != undefined) { + this.partId = v.partId; + + this.link.length = v.link.length; + for (let i = 0; i < v.link.length; i++) { + this.link[i] = v.link[i].clone(); + } + } + } + + /** + * =演算子のオーバーロード + */ + public assignment(v: PartData): PartData { + this.partId = v.partId; + + let dstIndex: number = this.link.length; + this.link.length += v.link.length; + for (const partData of v.link) { + this.link[dstIndex++] = partData.clone(); + } + + return this; + } + + /** + * 初期化 + * @param model 初期化に使用するモデル + */ + public initialize(model: CubismModel): void { + this.parameterIndex = model.getParameterIndex(this.partId); + this.partIndex = model.getPartIndex(this.partId); + + model.setParameterValueByIndex(this.parameterIndex, 1); + } + + /** + * オブジェクトのコピーを生成する + */ + public clone(): PartData { + const clonePartData: PartData = new PartData(); + + clonePartData.partId = this.partId; + clonePartData.parameterIndex = this.parameterIndex; + clonePartData.partIndex = this.partIndex; + clonePartData.link = new Array(); + + clonePartData.link.length = this.link.length; + for (let i = 0; i < this.link.length; i++) { + clonePartData.link[i] = this.link[i].clone(); + } + + return clonePartData; + } + + partId: CubismIdHandle; // パーツID + parameterIndex: number; // パラメータのインデックス + partIndex: number; // パーツのインデックス + link: Array; // 連動するパラメータ +} + +// Namespace definition for compatibility. +import * as $ from './cubismpose'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPose = $.CubismPose; + export type CubismPose = $.CubismPose; + export const PartData = $.PartData; + export type PartData = $.PartData; +} diff --git a/avatar-h5-renderer/framework/src/icubismallcator.ts b/avatar-h5-renderer/framework/src/icubismallcator.ts new file mode 100644 index 0000000..5e10ace --- /dev/null +++ b/avatar-h5-renderer/framework/src/icubismallcator.ts @@ -0,0 +1,51 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * メモリアロケーションを抽象化したクラス + * + * メモリ確保・解放処理をプラットフォーム側で実装して + * フレームワークから呼び出すためのインターフェース + */ +export abstract class ICubismAllocator { + /** + * アラインメント制約なしのヒープ・メモリーを確保します + * + * @param size 確保するバイト数 + * @return 成功すると割り当てられたメモリのアドレス。そうでなければ'0'を返す + */ + public abstract allocate(size: number): any; + + /** + * アラインメント制約なしのヒープ・メモリーを解放します。 + * + * @param memory 解放するメモリのアドレス + */ + public abstract deallocate(memory: any): void; + + /** + * アラインメント制約有のヒープ・メモリーを確保します。 + * @param size 確保するバイト数 + * @param alignment メモリーブロックのアラインメント幅 + * @return 成功すると割り当てられたメモリのアドレス。そうでなければ'0'を返す + */ + public abstract allocateAligned(size: number, alignment: number): any; + + /** + * アラインメント制約ありのヒープ・メモリーを解放します。 + * @param alignedMemory 解放するメモリのアドレス + */ + public abstract deallocateAligned(alignedMemory: any): void; +} + +// Namespace definition for compatibility. +import * as $ from './icubismallcator'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const ICubismAllocator = $.ICubismAllocator; + export type ICubismAllocator = $.ICubismAllocator; +} diff --git a/avatar-h5-renderer/framework/src/icubismmodelsetting.ts b/avatar-h5-renderer/framework/src/icubismmodelsetting.ts new file mode 100644 index 0000000..b11968e --- /dev/null +++ b/avatar-h5-renderer/framework/src/icubismmodelsetting.ts @@ -0,0 +1,202 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from './id/cubismid'; + +/** + * モデル設定情報を取り扱う関数を宣言した純粋仮想クラス。 + * + * このクラスを継承することで、モデル設定情報を取り扱うクラスになる。 + */ +export abstract class ICubismModelSetting { + /** + * Mocファイルの名前を取得する + * @return Mocファイルの名前 + */ + public abstract getModelFileName(): string; + + /** + * モデルが使用するテクスチャの数を取得する + * テクスチャの数 + */ + public abstract getTextureCount(): number; + + /** + * テクスチャが配置されたディレクトリの名前を取得する + * @return テクスチャが配置されたディレクトリの名前 + */ + public abstract getTextureDirectory(): string; + + /** + * モデルが使用するテクスチャの名前を取得する + * @param index 配列のインデックス値 + * @return テクスチャの名前 + */ + public abstract getTextureFileName(index: number): string; + + /** + * モデルに設定された当たり判定の数を取得する + * @return モデルに設定された当たり判定の数 + */ + public abstract getHitAreasCount(): number; + + /** + * 当たり判定に設定されたIDを取得する + * + * @param index 配列のindex + * @return 当たり判定に設定されたID + */ + public abstract getHitAreaId(index: number): CubismIdHandle; + + /** + * 当たり判定に設定された名前を取得する + * @param index 配列のインデックス値 + * @return 当たり判定に設定された名前 + */ + public abstract getHitAreaName(index: number): string; + + /** + * 物理演算設定ファイルの名前を取得する + * @return 物理演算設定ファイルの名前 + */ + public abstract getPhysicsFileName(): string; + + /** + * パーツ切り替え設定ファイルの名前を取得する + * @return パーツ切り替え設定ファイルの名前 + */ + public abstract getPoseFileName(): string; + + /** + * 表情設定ファイルの数を取得する + * @return 表情設定ファイルの数 + */ + public abstract getExpressionCount(): number; + + /** + * 表情設定ファイルを識別する名前(別名)を取得する + * @param index 配列のインデックス値 + * @return 表情の名前 + */ + public abstract getExpressionName(index: number): string; + + /** + * 表情設定ファイルの名前を取得する + * @param index 配列のインデックス値 + * @return 表情設定ファイルの名前 + */ + public abstract getExpressionFileName(index: number): string; + + /** + * モーショングループの数を取得する + * @return モーショングループの数 + */ + public abstract getMotionGroupCount(): number; + + /** + * モーショングループの名前を取得する + * @param index 配列のインデックス値 + * @return モーショングループの名前 + */ + public abstract getMotionGroupName(index: number): string; + + /** + * モーショングループに含まれるモーションの数を取得する + * @param groupName モーショングループの名前 + * @return モーショングループの数 + */ + public abstract getMotionCount(groupName: string): number; + + /** + * グループ名とインデックス値からモーションファイル名を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return モーションファイルの名前 + */ + public abstract getMotionFileName(groupName: string, index: number): string; + + /** + * モーションに対応するサウンドファイルの名前を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return サウンドファイルの名前 + */ + public abstract getMotionSoundFileName( + groupName: string, + index: number + ): string; + + /** + * モーション開始時のフェードイン処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードイン処理時間[秒] + */ + public abstract getMotionFadeInTimeValue( + groupName: string, + index: number + ): number; + + /** + * モーション終了時のフェードアウト処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードアウト処理時間[秒] + */ + public abstract getMotionFadeOutTimeValue( + groupName: string, + index: number + ): number; + + /** + * ユーザーデータのファイル名を取得する + * @return ユーザーデータのファイル名 + */ + public abstract getUserDataFile(): string; + + /** + * レイアウト情報を取得する + * @param outLayoutMap Mapクラスのインスタンス + * @return true レイアウト情報が存在する + * @return false レイアウト情報が存在しない + */ + public abstract getLayoutMap(outLayoutMap: Map): boolean; + + /** + * 目パチに関連付けられたパラメータの数を取得する + * @return 目パチに関連付けられたパラメータの数 + */ + public abstract getEyeBlinkParameterCount(): number; + + /** + * 目パチに関連付けられたパラメータのIDを取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public abstract getEyeBlinkParameterId(index: number): CubismIdHandle; + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @return リップシンクに関連付けられたパラメータの数 + */ + public abstract getLipSyncParameterCount(): number; + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public abstract getLipSyncParameterId(index: number): CubismIdHandle; +} + +// Namespace definition for compatibility. +import * as $ from './icubismmodelsetting'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const ICubismModelSetting = $.ICubismModelSetting; + export type ICubismModelSetting = $.ICubismModelSetting; +} diff --git a/avatar-h5-renderer/framework/src/id/cubismid.ts b/avatar-h5-renderer/framework/src/id/cubismid.ts new file mode 100644 index 0000000..0ac2c6f --- /dev/null +++ b/avatar-h5-renderer/framework/src/id/cubismid.ts @@ -0,0 +1,85 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * パラメータ名・パーツ名・Drawable名を保持 + * + * パラメータ名・パーツ名・Drawable名を保持するクラス。 + * + * @note 指定したID文字列からCubismIdを取得する際はこのクラスの生成メソッドを呼ばず、 + * CubismIdManager().getId(id)を使用してください + */ +export class CubismId { + /** + * 内部で使用するCubismIdクラス生成メソッド + * + * @param id ID文字列 + * @return CubismId + * @note 指定したID文字列からCubismIdを取得する際は + * CubismIdManager().getId(id)を使用してください + */ + public static createIdInternal(id: string) { + return new CubismId(id); + } + + /** + * ID名を取得する + */ + public getString() { + return this._id; + } + + /** + * idを比較 + * @param c 比較するid + * @return 同じならばtrue,異なっていればfalseを返す + */ + public isEqual(c: string | CubismId): boolean { + if (typeof c === 'string') { + return this._id == c; + } else if (c instanceof CubismId) { + return this._id == c._id; + } + return false; + } + + /** + * idを比較 + * @param c 比較するid + * @return 同じならばtrue,異なっていればfalseを返す + */ + public isNotEqual(c: string | CubismId): boolean { + if (typeof c == 'string') { + return !(this._id == c); + } else if (c instanceof CubismId) { + return !(this._id == c._id); + } + return false; + } + + /** + * プライベートコンストラクタ + * + * @note ユーザーによる生成は許可しません + */ + private constructor(id: string) { + this._id = id; + } + + private _id: string; // ID名 +} + +export declare type CubismIdHandle = CubismId; + +// Namespace definition for compatibility. +import * as $ from './cubismid'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismId = $.CubismId; + export type CubismId = $.CubismId; + export type CubismIdHandle = $.CubismIdHandle; +} diff --git a/avatar-h5-renderer/framework/src/id/cubismidmanager.ts b/avatar-h5-renderer/framework/src/id/cubismidmanager.ts new file mode 100644 index 0000000..5a7a400 --- /dev/null +++ b/avatar-h5-renderer/framework/src/id/cubismidmanager.ts @@ -0,0 +1,114 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismId } from './cubismid'; + +/** + * ID名の管理 + * + * ID名を管理する。 + */ +export class CubismIdManager { + /** + * コンストラクタ + */ + public constructor() { + this._ids = new Array(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for (let i = 0; i < this._ids.length; ++i) { + this._ids[i] = void 0; + } + this._ids = null; + } + + /** + * ID名をリストから登録 + * + * @param ids ID名リスト + * @param count IDの個数 + */ + public registerIds(ids: string[]): void { + for (let i = 0; i < ids.length; i++) { + this.registerId(ids[i]); + } + } + + /** + * ID名を登録 + * + * @param id ID名 + */ + public registerId(id: string): CubismId { + let result: CubismId = null; + + if ('string' == typeof id) { + if ((result = this.findId(id)) != null) { + return result; + } + + result = CubismId.createIdInternal(id); + this._ids.push(result); + } else { + return this.registerId(id); + } + + return result; + } + + /** + * ID名からIDを取得する + * + * @param id ID名 + */ + public getId(id: string): CubismId { + return this.registerId(id); + } + + /** + * ID名からIDの確認 + * + * @return true 存在する + * @return false 存在しない + */ + public isExist(id: string): boolean { + if ('string' == typeof id) { + return this.findId(id) != null; + } + return this.isExist(id); + } + + /** + * ID名からIDを検索する。 + * + * @param id ID名 + * @return 登録されているID。なければNULL。 + */ + private findId(id: string): CubismId { + for (let i = 0; i < this._ids.length; ++i) { + if (this._ids[i].getString() == id) { + return this._ids[i]; + } + } + + return null; + } + + private _ids: Array; // 登録されているIDのリスト +} + +// Namespace definition for compatibility. +import * as $ from './cubismidmanager'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismIdManager = $.CubismIdManager; + export type CubismIdManager = $.CubismIdManager; +} diff --git a/avatar-h5-renderer/framework/src/live2dcubismframework.ts b/avatar-h5-renderer/framework/src/live2dcubismframework.ts new file mode 100644 index 0000000..ada25a8 --- /dev/null +++ b/avatar-h5-renderer/framework/src/live2dcubismframework.ts @@ -0,0 +1,288 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdManager } from './id/cubismidmanager'; +import { CubismRenderer } from './rendering/cubismrenderer'; +import { + CSM_ASSERT, + CubismLogInfo, + CubismLogWarning +} from './utils/cubismdebug'; +import { Value } from './utils/cubismjson'; + +export function strtod(s: string, endPtr: string[]): number { + let index = 0; + for (let i = 1; ; i++) { + const testC: string = s.slice(i - 1, i); + + // 指数・マイナスの可能性があるのでスキップする + if (testC == 'e' || testC == '-' || testC == 'E') { + continue; + } // 文字列の範囲を広げていく + + const test: string = s.substring(0, i); + const number = Number(test); + if (isNaN(number)) { + // 数値として認識できなくなったので終了 + break; + } // 最後に数値としてできたindexを格納しておく + + index = i; + } + let d = parseFloat(s); // パースした数値 + + if (isNaN(d)) { + // 数値として認識できなくなったので終了 + d = NaN; + } + + endPtr[0] = s.slice(index); // 後続の文字列 + return d; +} + +// ファイルスコープの変数を初期化 + +let s_isStarted = false; +let s_isInitialized = false; +let s_option: Option = null; +let s_cubismIdManager: CubismIdManager = null; + +/** + * Framework内で使う定数の宣言 + */ +export const Constant = Object.freeze>({ + vertexOffset: 0, // メッシュ頂点のオフセット値 + vertexStep: 2 // メッシュ頂点のステップ値 +}); + +export function csmDelete(address: T): void { + if (!address) { + return; + } + + address = void 0; +} + +/** + * Live2D Cubism SDK Original Workflow SDKのエントリポイント + * 利用開始時はCubismFramework.initialize()を呼び、CubismFramework.dispose()で終了する。 + */ +export class CubismFramework { + /** + * Cubism FrameworkのAPIを使用可能にする。 + * APIを実行する前に必ずこの関数を実行すること。 + * 一度準備が完了して以降は、再び実行しても内部処理がスキップされます。 + * + * @param option Optionクラスのインスタンス + * + * @return 準備処理が完了したらtrueが返ります。 + */ + public static startUp(option: Option = null): boolean { + if (s_isStarted) { + CubismLogInfo('CubismFramework.startUp() is already done.'); + return s_isStarted; + } + + s_option = option; + + if (s_option != null) { + Live2DCubismCore.Logging.csmSetLogFunction(s_option.logFunction); + } + + s_isStarted = true; + + // Live2D Cubism Coreバージョン情報を表示 + if (s_isStarted) { + const version: number = Live2DCubismCore.Version.csmGetVersion(); + const major: number = (version & 0xff000000) >> 24; + const minor: number = (version & 0x00ff0000) >> 16; + const patch: number = version & 0x0000ffff; + const versionNumber: number = version; + + CubismLogInfo( + `Live2D Cubism Core version: {0}.{1}.{2} ({3})`, + ('00' + major).slice(-2), + ('00' + minor).slice(-2), + ('0000' + patch).slice(-4), + versionNumber + ); + } + + CubismLogInfo('CubismFramework.startUp() is complete.'); + + return s_isStarted; + } + + /** + * StartUp()で初期化したCubismFrameworkの各パラメータをクリアします。 + * Dispose()したCubismFrameworkを再利用する際に利用してください。 + */ + public static cleanUp(): void { + s_isStarted = false; + s_isInitialized = false; + s_option = null; + s_cubismIdManager = null; + } + + /** + * Cubism Framework内のリソースを初期化してモデルを表示可能な状態にします。
+ * 再度Initialize()するには先にDispose()を実行する必要があります。 + * + * @param memorySize 初期化時メモリ量 [byte(s)] + * 複数モデル表示時などにモデルが更新されない際に使用してください。 + * 指定する際は必ず1024*1024*16 byte(16MB)以上の値を指定してください。 + * それ以外はすべて1024*1024*16 byteに丸めます。 + */ + public static initialize(memorySize = 0): void { + CSM_ASSERT(s_isStarted); + if (!s_isStarted) { + CubismLogWarning('CubismFramework is not started.'); + return; + } + + // --- s_isInitializedによる連続初期化ガード --- + // 連続してリソース確保が行われないようにする。 + // 再度Initialize()するには先にDispose()を実行する必要がある。 + if (s_isInitialized) { + CubismLogWarning( + 'CubismFramework.initialize() skipped, already initialized.' + ); + return; + } + + //---- static 初期化 ---- + Value.staticInitializeNotForClientCall(); + + s_cubismIdManager = new CubismIdManager(); + + // --- HACK: 初期化時メモリ量の拡張(単位byte) --- + // 複数モデル表示時などにモデルが更新されない際に使用してください。 + // 指定する際は必ず1024*1024*16 byte(16MB)以上の値を指定してください。 + // それ以外はすべて1024*1024*16 byteに丸めます。 + Live2DCubismCore.Memory.initializeAmountOfMemory(memorySize); + + s_isInitialized = true; + + CubismLogInfo('CubismFramework.initialize() is complete.'); + } + + /** + * Cubism Framework内の全てのリソースを解放します。 + * ただし、外部で確保されたリソースについては解放しません。 + * 外部で適切に破棄する必要があります。 + */ + public static dispose(): void { + CSM_ASSERT(s_isStarted); + if (!s_isStarted) { + CubismLogWarning('CubismFramework is not started.'); + return; + } + + // --- s_isInitializedによる未初期化解放ガード --- + // dispose()するには先にinitialize()を実行する必要がある。 + if (!s_isInitialized) { + // false...リソース未確保の場合 + CubismLogWarning('CubismFramework.dispose() skipped, not initialized.'); + return; + } + + Value.staticReleaseNotForClientCall(); + + s_cubismIdManager.release(); + s_cubismIdManager = null; + + // レンダラの静的リソース(シェーダプログラム他)を解放する + CubismRenderer.staticRelease(); + + s_isInitialized = false; + + CubismLogInfo('CubismFramework.dispose() is complete.'); + } + + /** + * Cubism FrameworkのAPIを使用する準備が完了したかどうか + * @return APIを使用する準備が完了していればtrueが返ります。 + */ + public static isStarted(): boolean { + return s_isStarted; + } + + /** + * Cubism Frameworkのリソース初期化がすでに行われているかどうか + * @return リソース確保が完了していればtrueが返ります + */ + public static isInitialized(): boolean { + return s_isInitialized; + } + + /** + * Core APIにバインドしたログ関数を実行する + * + * @praram message ログメッセージ + */ + public static coreLogFunction(message: string): void { + // Return if logging not possible. + if (!Live2DCubismCore.Logging.csmGetLogFunction()) { + return; + } + + Live2DCubismCore.Logging.csmGetLogFunction()(message); + } + + /** + * 現在のログ出力レベル設定の値を返す。 + * + * @return 現在のログ出力レベル設定の値 + */ + public static getLoggingLevel(): LogLevel { + if (s_option != null) { + return s_option.loggingLevel; + } + return LogLevel.LogLevel_Off; + } + + /** + * IDマネージャのインスタンスを取得する + * @return CubismManagerクラスのインスタンス + */ + public static getIdManager(): CubismIdManager { + return s_cubismIdManager; + } + + /** + * 静的クラスとして使用する + * インスタンス化させない + */ + private constructor() {} +} + +export class Option { + logFunction: Live2DCubismCore.csmLogFunction; // ログ出力の関数オブジェクト + loggingLevel: LogLevel; // ログ出力レベルの設定 +} + +/** + * ログ出力のレベル + */ +export enum LogLevel { + LogLevel_Verbose = 0, // 詳細ログ + LogLevel_Debug, // デバッグログ + LogLevel_Info, // Infoログ + LogLevel_Warning, // 警告ログ + LogLevel_Error, // エラーログ + LogLevel_Off // ログ出力無効 +} + +// Namespace definition for compatibility. +import * as $ from './live2dcubismframework'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const Constant = $.Constant; + export const csmDelete = $.csmDelete; + export const CubismFramework = $.CubismFramework; + export type CubismFramework = $.CubismFramework; +} diff --git a/avatar-h5-renderer/framework/src/math/cubismmath.ts b/avatar-h5-renderer/framework/src/math/cubismmath.ts new file mode 100644 index 0000000..031196e --- /dev/null +++ b/avatar-h5-renderer/framework/src/math/cubismmath.ts @@ -0,0 +1,376 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismVector2 } from './cubismvector2'; + +/** + * 数値計算などに使用するユーティリティクラス + */ +export class CubismMath { + static readonly Epsilon: number = 0.00001; + + /** + * 第一引数の値を最小値と最大値の範囲に収めた値を返す + * + * @param value 収められる値 + * @param min 範囲の最小値 + * @param max 範囲の最大値 + * @return 最小値と最大値の範囲に収めた値 + */ + static range(value: number, min: number, max: number): number { + if (value < min) { + value = min; + } else if (value > max) { + value = max; + } + + return value; + } + + /** + * サイン関数の値を求める + * + * @param x 角度値(ラジアン) + * @return サイン関数sin(x)の値 + */ + static sin(x: number): number { + return Math.sin(x); + } + + /** + * コサイン関数の値を求める + * + * @param x 角度値(ラジアン) + * @return コサイン関数cos(x)の値 + */ + static cos(x: number): number { + return Math.cos(x); + } + + /** + * 値の絶対値を求める + * + * @param x 絶対値を求める値 + * @return 値の絶対値 + */ + static abs(x: number): number { + return Math.abs(x); + } + + /** + * 平方根(ルート)を求める + * @param x -> 平方根を求める値 + * @return 値の平方根 + */ + static sqrt(x: number): number { + return Math.sqrt(x); + } + + /** + * 立方根を求める + * @param x -> 立方根を求める値 + * @return 値の立方根 + */ + static cbrt(x: number): number { + if (x === 0) { + return x; + } + + let cx: number = x; + const isNegativeNumber: boolean = cx < 0; + + if (isNegativeNumber) { + cx = -cx; + } + + let ret: number; + if (cx === Infinity) { + ret = Infinity; + } else { + ret = Math.exp(Math.log(cx) / 3); + ret = (cx / (ret * ret) + 2 * ret) / 3; + } + return isNegativeNumber ? -ret : ret; + } + + /** + * イージング処理されたサインを求める + * フェードイン・アウト時のイージングに利用できる + * + * @param value イージングを行う値 + * @return イージング処理されたサイン値 + */ + static getEasingSine(value: number): number { + if (value < 0.0) { + return 0.0; + } else if (value > 1.0) { + return 1.0; + } + + return 0.5 - 0.5 * this.cos(value * Math.PI); + } + + /** + * 大きい方の値を返す + * + * @param left 左辺の値 + * @param right 右辺の値 + * @return 大きい方の値 + */ + static max(left: number, right: number): number { + return left > right ? left : right; + } + + /** + * 小さい方の値を返す + * + * @param left 左辺の値 + * @param right 右辺の値 + * @return 小さい方の値 + */ + static min(left: number, right: number): number { + return left > right ? right : left; + } + + public static clamp(val: number, min: number, max: number): number { + if (val < min) { + return min; + } else if (max < val) { + return max; + } + return val; + } + + /** + * 角度値をラジアン値に変換する + * + * @param degrees 角度値 + * @return 角度値から変換したラジアン値 + */ + static degreesToRadian(degrees: number): number { + return (degrees / 180.0) * Math.PI; + } + + /** + * ラジアン値を角度値に変換する + * + * @param radian ラジアン値 + * @return ラジアン値から変換した角度値 + */ + static radianToDegrees(radian: number): number { + return (radian * 180.0) / Math.PI; + } + + /** + * 2つのベクトルからラジアン値を求める + * + * @param from 始点ベクトル + * @param to 終点ベクトル + * @return ラジアン値から求めた方向ベクトル + */ + static directionToRadian(from: CubismVector2, to: CubismVector2): number { + const q1: number = Math.atan2(to.y, to.x); + const q2: number = Math.atan2(from.y, from.x); + + let ret: number = q1 - q2; + + while (ret < -Math.PI) { + ret += Math.PI * 2.0; + } + + while (ret > Math.PI) { + ret -= Math.PI * 2.0; + } + + return ret; + } + + /** + * 2つのベクトルから角度値を求める + * + * @param from 始点ベクトル + * @param to 終点ベクトル + * @return 角度値から求めた方向ベクトル + */ + static directionToDegrees(from: CubismVector2, to: CubismVector2): number { + const radian: number = this.directionToRadian(from, to); + let degree: number = this.radianToDegrees(radian); + + if (to.x - from.x > 0.0) { + degree = -degree; + } + + return degree; + } + + /** + * ラジアン値を方向ベクトルに変換する。 + * + * @param totalAngle ラジアン値 + * @return ラジアン値から変換した方向ベクトル + */ + + static radianToDirection(totalAngle: number): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(); + + ret.x = this.sin(totalAngle); + ret.y = this.cos(totalAngle); + + return ret; + } + + /** + * 三次方程式の三次項の係数が0になったときに補欠的に二次方程式の解をもとめる。 + * a * x^2 + b * x + c = 0 + * + * @param a -> 二次項の係数値 + * @param b -> 一次項の係数値 + * @param c -> 定数項の値 + * @return 二次方程式の解 + */ + static quadraticEquation(a: number, b: number, c: number): number { + if (this.abs(a) < CubismMath.Epsilon) { + if (this.abs(b) < CubismMath.Epsilon) { + return -c; + } + return -c / b; + } + + return -(b + this.sqrt(b * b - 4.0 * a * c)) / (2.0 * a); + } + + /** + * カルダノの公式によってベジェのt値に該当する3次方程式の解を求める。 + * 重解になったときには0.0~1.0の値になる解を返す。 + * + * a * x^3 + b * x^2 + c * x + d = 0 + * + * @param a -> 三次項の係数値 + * @param b -> 二次項の係数値 + * @param c -> 一次項の係数値 + * @param d -> 定数項の値 + * @return 0.0~1.0の間にある解 + */ + static cardanoAlgorithmForBezier( + a: number, + b: number, + c: number, + d: number + ): number { + if (this.abs(a) < CubismMath.Epsilon) { + return this.range(this.quadraticEquation(b, c, d), 0.0, 1.0); + } + + const ba: number = b / a; + const ca: number = c / a; + const da: number = d / a; + + const p: number = (3.0 * ca - ba * ba) / 3.0; + const p3: number = p / 3.0; + const q: number = (2.0 * ba * ba * ba - 9.0 * ba * ca + 27.0 * da) / 27.0; + const q2: number = q / 2.0; + const discriminant: number = q2 * q2 + p3 * p3 * p3; + + const center = 0.5; + const threshold: number = center + 0.01; + + if (discriminant < 0.0) { + const mp3: number = -p / 3.0; + const mp33: number = mp3 * mp3 * mp3; + const r: number = this.sqrt(mp33); + const t: number = -q / (2.0 * r); + const cosphi: number = this.range(t, -1.0, 1.0); + const phi: number = Math.acos(cosphi); + const crtr: number = this.cbrt(r); + const t1: number = 2.0 * crtr; + + const root1: number = t1 * this.cos(phi / 3.0) - ba / 3.0; + if (this.abs(root1 - center) < threshold) { + return this.range(root1, 0.0, 1.0); + } + + const root2: number = + t1 * this.cos((phi + 2.0 * Math.PI) / 3.0) - ba / 3.0; + if (this.abs(root2 - center) < threshold) { + return this.range(root2, 0.0, 1.0); + } + + const root3: number = + t1 * this.cos((phi + 4.0 * Math.PI) / 3.0) - ba / 3.0; + return this.range(root3, 0.0, 1.0); + } + + if (discriminant == 0.0) { + let u1: number; + if (q2 < 0.0) { + u1 = this.cbrt(-q2); + } else { + u1 = -this.cbrt(q2); + } + + const root1: number = 2.0 * u1 - ba / 3.0; + if (this.abs(root1 - center) < threshold) { + return this.range(root1, 0.0, 1.0); + } + + const root2: number = -u1 - ba / 3.0; + return this.range(root2, 0.0, 1.0); + } + + const sd: number = this.sqrt(discriminant); + const u1: number = this.cbrt(sd - q2); + const v1: number = this.cbrt(sd + q2); + const root1: number = u1 - v1 - ba / 3.0; + return this.range(root1, 0.0, 1.0); + } + + /** + * 浮動小数点の余りを求める。 + * + * @param dividend 被除数(割られる値) + * @param divisor 除数(割る値) + * @return 余り + */ + static mod(dividend: number, divisor: number): number { + if ( + !isFinite(dividend) || + divisor === 0 || + isNaN(dividend) || + isNaN(divisor) + ) { + console.warn( + `divided: ${dividend}, divisor: ${divisor} mod() returns 'NaN'.` + ); + return NaN; + } + + // 絶対値に変換する。 + const absDividend = Math.abs(dividend); + const absDivisor = Math.abs(divisor); + + // 絶対値で割り算する。 + let result = + absDividend - Math.floor(absDividend / absDivisor) * absDivisor; + + // 符号を被除数のものに指定する。 + result *= Math.sign(dividend); + return result; + } + + /** + * コンストラクタ + */ + private constructor() {} +} + +// Namespace definition for compatibility. +import * as $ from './cubismmath'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMath = $.CubismMath; + export type CubismMath = $.CubismMath; +} diff --git a/avatar-h5-renderer/framework/src/math/cubismmatrix44.ts b/avatar-h5-renderer/framework/src/math/cubismmatrix44.ts new file mode 100644 index 0000000..4510a8f --- /dev/null +++ b/avatar-h5-renderer/framework/src/math/cubismmatrix44.ts @@ -0,0 +1,361 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from './cubismmath'; + +/** + * 4x4の行列 + * + * 4x4行列の便利クラス。 + */ +export class CubismMatrix44 { + /** + * コンストラクタ + */ + public constructor() { + this._tr = new Float32Array(16); // 4 * 4のサイズ + this.loadIdentity(); + } + + /** + * 受け取った2つの行列の乗算を行う。 + * + * @param a 行列a + * @param b 行列b + * + * @return 乗算結果の行列 + */ + public static multiply( + a: Float32Array, + b: Float32Array, + dst: Float32Array + ): void { + const c: Float32Array = new Float32Array([ + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0 + ]); + + const n = 4; + + for (let i = 0; i < n; ++i) { + for (let j = 0; j < n; ++j) { + for (let k = 0; k < n; ++k) { + c[j + i * 4] += a[k + i * 4] * b[j + k * 4]; + } + } + } + + for (let i = 0; i < 16; ++i) { + dst[i] = c[i]; + } + } + + /** + * 単位行列に初期化する + */ + public loadIdentity(): void { + const c: Float32Array = new Float32Array([ + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 1.0 + ]); + + this.setMatrix(c); + } + + /** + * 行列を設定 + * + * @param tr 16個の浮動小数点数で表される4x4の行列 + */ + public setMatrix(tr: Float32Array): void { + for (let i = 0; i < 16; ++i) { + this._tr[i] = tr[i]; + } + } + + /** + * 行列を浮動小数点数の配列で取得 + * + * @return 16個の浮動小数点数で表される4x4の行列 + */ + public getArray(): Float32Array { + return this._tr; + } + + /** + * X軸の拡大率を取得 + * + * @return X軸の拡大率 + */ + public getScaleX(): number { + return this._tr[0]; + } + + /** + * Y軸の拡大率を取得する + * + * @return Y軸の拡大率 + */ + public getScaleY(): number { + return this._tr[5]; + } + + /** + * X軸の移動量を取得 + * + * @return X軸の移動量 + */ + public getTranslateX(): number { + return this._tr[12]; + } + + /** + * Y軸の移動量を取得 + * + * @return Y軸の移動量 + */ + public getTranslateY(): number { + return this._tr[13]; + } + + /** + * X軸の値を現在の行列で計算 + * + * @param src X軸の値 + * + * @return 現在の行列で計算されたX軸の値 + */ + public transformX(src: number): number { + return this._tr[0] * src + this._tr[12]; + } + + /** + * Y軸の値を現在の行列で計算 + * + * @param src Y軸の値 + * + * @return 現在の行列で計算されたY軸の値 + */ + public transformY(src: number): number { + return this._tr[5] * src + this._tr[13]; + } + + /** + * X軸の値を現在の行列で逆計算 + */ + public invertTransformX(src: number): number { + return (src - this._tr[12]) / this._tr[0]; + } + + /** + * Y軸の値を現在の行列で逆計算 + */ + public invertTransformY(src: number): number { + return (src - this._tr[13]) / this._tr[5]; + } + + /** + * 現在の行列の位置を起点にして移動 + * + * 現在の行列の位置を起点にして相対的に移動する。 + * + * @param x X軸の移動量 + * @param y Y軸の移動量 + */ + public translateRelative(x: number, y: number): void { + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + x, + y, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 現在の行列の位置を移動 + * + * 現在の行列の位置を指定した位置へ移動する + * + * @param x X軸の移動量 + * @param y y軸の移動量 + */ + public translate(x: number, y: number): void { + this._tr[12] = x; + this._tr[13] = y; + } + + /** + * 現在の行列のX軸の位置を指定した位置へ移動する + * + * @param x X軸の移動量 + */ + public translateX(x: number): void { + this._tr[12] = x; + } + + /** + * 現在の行列のY軸の位置を指定した位置へ移動する + * + * @param y Y軸の移動量 + */ + public translateY(y: number): void { + this._tr[13] = y; + } + + /** + * 現在の行列の拡大率を相対的に設定する + * + * @param x X軸の拡大率 + * @param y Y軸の拡大率 + */ + public scaleRelative(x: number, y: number): void { + const tr1: Float32Array = new Float32Array([ + x, + 0.0, + 0.0, + 0.0, + 0.0, + y, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 現在の行列の拡大率を指定した倍率に設定する + * + * @param x X軸の拡大率 + * @param y Y軸の拡大率 + */ + public scale(x: number, y: number): void { + this._tr[0] = x; + this._tr[5] = y; + } + + /** + * 引数で与えられた行列にこの行列を乗算する。 + * (引数で与えられた行列) * (この行列) + * + * @note 関数名と実際の計算内容に乖離があるため、今後計算順が修正される可能性があります。 + * @param m 行列 + */ + public multiplyByMatrix(m: CubismMatrix44): void { + CubismMatrix44.multiply(m.getArray(), this._tr, this._tr); + } + + /** + * 現在の行列の逆行列を求める。 + * + * @return 現在の行列で計算された逆行列の値を返す + */ + public getInvert(): CubismMatrix44 { + const r00 = this._tr[0]; + const r10 = this._tr[1]; + const r20 = this._tr[2]; + const r01 = this._tr[4]; + const r11 = this._tr[5]; + const r21 = this._tr[6]; + const r02 = this._tr[8]; + const r12 = this._tr[9]; + const r22 = this._tr[10]; + + const tx = this._tr[12]; + const ty = this._tr[13]; + const tz = this._tr[14]; + + const det = + r00 * (r11 * r22 - r12 * r21) - + r01 * (r10 * r22 - r12 * r20) + + r02 * (r10 * r21 - r11 * r20); + + const dst = new CubismMatrix44(); + + if (CubismMath.abs(det) < CubismMath.Epsilon) { + dst.loadIdentity(); + return dst; + } + + const invDet = 1.0 / det; + + const inv00 = (r11 * r22 - r12 * r21) * invDet; + const inv01 = -(r01 * r22 - r02 * r21) * invDet; + const inv02 = (r01 * r12 - r02 * r11) * invDet; + const inv10 = -(r10 * r22 - r12 * r20) * invDet; + const inv11 = (r00 * r22 - r02 * r20) * invDet; + const inv12 = -(r00 * r12 - r02 * r10) * invDet; + const inv20 = (r10 * r21 - r11 * r20) * invDet; + const inv21 = -(r00 * r21 - r01 * r20) * invDet; + const inv22 = (r00 * r11 - r01 * r10) * invDet; + + dst._tr[0] = inv00; + dst._tr[1] = inv10; + dst._tr[2] = inv20; + dst._tr[3] = 0.0; + dst._tr[4] = inv01; + dst._tr[5] = inv11; + dst._tr[6] = inv21; + dst._tr[7] = 0.0; + dst._tr[8] = inv02; + dst._tr[9] = inv12; + dst._tr[10] = inv22; + dst._tr[11] = 0.0; + + dst._tr[12] = -(inv00 * tx + inv01 * ty + inv02 * tz); + dst._tr[13] = -(inv10 * tx + inv11 * ty + inv12 * tz); + dst._tr[14] = -(inv20 * tx + inv21 * ty + inv22 * tz); + dst._tr[15] = 1.0; + + return dst; + } + + /** + * オブジェクトのコピーを生成する + */ + public clone(): CubismMatrix44 { + const cloneMatrix: CubismMatrix44 = new CubismMatrix44(); + + for (let i = 0; i < this._tr.length; i++) { + cloneMatrix._tr[i] = this._tr[i]; + } + + return cloneMatrix; + } + + protected _tr: Float32Array; // 4x4行列データ +} + +// Namespace definition for compatibility. +import * as $ from './cubismmatrix44'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMatrix44 = $.CubismMatrix44; + export type CubismMatrix44 = $.CubismMatrix44; +} diff --git a/avatar-h5-renderer/framework/src/math/cubismmodelmatrix.ts b/avatar-h5-renderer/framework/src/math/cubismmodelmatrix.ts new file mode 100644 index 0000000..0f1f263 --- /dev/null +++ b/avatar-h5-renderer/framework/src/math/cubismmodelmatrix.ts @@ -0,0 +1,217 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMatrix44 } from './cubismmatrix44'; + +/** + * モデル座標設定用の4x4行列 + * + * モデル座標設定用の4x4行列クラス + */ +export class CubismModelMatrix extends CubismMatrix44 { + /** + * コンストラクタ + * + * @param w 横幅 + * @param h 縦幅 + */ + constructor(w?: number, h?: number) { + super(); + + this._width = w !== undefined ? w : 0.0; + this._height = h !== undefined ? h : 0.0; + + this.setHeight(2.0); + } + + /** + * 横幅を設定 + * + * @param w 横幅 + */ + public setWidth(w: number): void { + const scaleX: number = w / this._width; + const scaleY: number = scaleX; + this.scale(scaleX, scaleY); + } + + /** + * 縦幅を設定 + * @param h 縦幅 + */ + public setHeight(h: number): void { + const scaleX: number = h / this._height; + const scaleY: number = scaleX; + this.scale(scaleX, scaleY); + } + + /** + * 位置を設定 + * + * @param x X軸の位置 + * @param y Y軸の位置 + */ + public setPosition(x: number, y: number): void { + this.translate(x, y); + } + + /** + * 中心位置を設定 + * + * @param x X軸の中心位置 + * @param y Y軸の中心位置 + * + * @note widthかheightを設定したあとでないと、拡大率が正しく取得できないためずれる。 + */ + public setCenterPosition(x: number, y: number) { + this.centerX(x); + this.centerY(y); + } + + /** + * 上辺の位置を設定する + * + * @param y 上辺のY軸位置 + */ + public top(y: number): void { + this.setY(y); + } + + /** + * 下辺の位置を設定する + * + * @param y 下辺のY軸位置 + */ + public bottom(y: number) { + const h: number = this._height * this.getScaleY(); + + this.translateY(y - h); + } + + /** + * 左辺の位置を設定 + * + * @param x 左辺のX軸位置 + */ + public left(x: number): void { + this.setX(x); + } + + /** + * 右辺の位置を設定 + * + * @param x 右辺のX軸位置 + */ + public right(x: number): void { + const w = this._width * this.getScaleX(); + + this.translateX(x - w); + } + + /** + * X軸の中心位置を設定 + * + * @param x X軸の中心位置 + */ + public centerX(x: number): void { + const w = this._width * this.getScaleX(); + + this.translateX(x - w / 2.0); + } + + /** + * X軸の位置を設定 + * + * @param x X軸の位置 + */ + public setX(x: number): void { + this.translateX(x); + } + + /** + * Y軸の中心位置を設定 + * + * @param y Y軸の中心位置 + */ + public centerY(y: number): void { + const h: number = this._height * this.getScaleY(); + + this.translateY(y - h / 2.0); + } + + /** + * Y軸の位置を設定する + * + * @param y Y軸の位置 + */ + public setY(y: number): void { + this.translateY(y); + } + + /** + * レイアウト情報から位置を設定 + * + * @param layout レイアウト情報 + */ + public setupFromLayout(layout: Map): void { + const keyWidth = 'width'; + const keyHeight = 'height'; + const keyX = 'x'; + const keyY = 'y'; + const keyCenterX = 'center_x'; + const keyCenterY = 'center_y'; + const keyTop = 'top'; + const keyBottom = 'bottom'; + const keyLeft = 'left'; + const keyRight = 'right'; + + for (const item of layout) { + const key: string = item[0]; + const value: number = item[1]; + + if (key == keyWidth) { + this.setWidth(value); + } else if (key == keyHeight) { + this.setHeight(value); + } + } + + for (const item of layout) { + const key: string = item[0]; + const value: number = item[1]; + + if (key == keyX) { + this.setX(value); + } else if (key == keyY) { + this.setY(value); + } else if (key == keyCenterX) { + this.centerX(value); + } else if (key == keyCenterY) { + this.centerY(value); + } else if (key == keyTop) { + this.top(value); + } else if (key == keyBottom) { + this.bottom(value); + } else if (key == keyLeft) { + this.left(value); + } else if (key == keyRight) { + this.right(value); + } + } + } + + private _width: number; // 横幅 + private _height: number; // 縦幅 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodelmatrix'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModelMatrix = $.CubismModelMatrix; + export type CubismModelMatrix = $.CubismModelMatrix; +} diff --git a/avatar-h5-renderer/framework/src/math/cubismtargetpoint.ts b/avatar-h5-renderer/framework/src/math/cubismtargetpoint.ts new file mode 100644 index 0000000..9ee2e59 --- /dev/null +++ b/avatar-h5-renderer/framework/src/math/cubismtargetpoint.ts @@ -0,0 +1,169 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from './cubismmath'; + +const FrameRate = 30; +const Epsilon = 0.01; + +/** + * 顔の向きの制御機能 + * + * 顔の向きの制御機能を提供するクラス。 + */ +export class CubismTargetPoint { + /** + * コンストラクタ + */ + public constructor() { + this._faceTargetX = 0.0; + this._faceTargetY = 0.0; + this._faceX = 0.0; + this._faceY = 0.0; + this._faceVX = 0.0; + this._faceVY = 0.0; + this._lastTimeSeconds = 0.0; + this._userTimeSeconds = 0.0; + } + + /** + * 更新処理 + */ + public update(deltaTimeSeconds: number): void { + // デルタ時間を加算する + this._userTimeSeconds += deltaTimeSeconds; + + // 首を中央から左右に振るときの平均的な速さは 秒速度。加速・減速を考慮して、その2倍を最高速度とする + // 顔の振り具合を、中央(0.0)から、左右は(+-1.0)とする + const faceParamMaxV: number = 40.0 / 10.0; // 7.5秒間に40分移動(5.3/sc) + const maxV: number = (faceParamMaxV * 1.0) / FrameRate; // 1frameあたりに変化できる速度の上限 + + if (this._lastTimeSeconds == 0.0) { + this._lastTimeSeconds = this._userTimeSeconds; + return; + } + + const deltaTimeWeight: number = + (this._userTimeSeconds - this._lastTimeSeconds) * FrameRate; + this._lastTimeSeconds = this._userTimeSeconds; + + // 最高速度になるまでの時間を + const timeToMaxSpeed = 0.15; + const frameToMaxSpeed: number = timeToMaxSpeed * FrameRate; // sec * frame/sec + const maxA: number = (deltaTimeWeight * maxV) / frameToMaxSpeed; // 1frameあたりの加速度 + + // 目指す向きは、(dx, dy)方向のベクトルとなる + const dx: number = this._faceTargetX - this._faceX; + const dy: number = this._faceTargetY - this._faceY; + + if (CubismMath.abs(dx) <= Epsilon && CubismMath.abs(dy) <= Epsilon) { + return; // 変化なし + } + + // 速度の最大よりも大きい場合は、速度を落とす + const d: number = CubismMath.sqrt(dx * dx + dy * dy); + + // 進行方向の最大速度ベクトル + const vx: number = (maxV * dx) / d; + const vy: number = (maxV * dy) / d; + + // 現在の速度から、新規速度への変化(加速度)を求める + let ax: number = vx - this._faceVX; + let ay: number = vy - this._faceVY; + + const a: number = CubismMath.sqrt(ax * ax + ay * ay); + + // 加速のとき + if (a < -maxA || a > maxA) { + ax *= maxA / a; + ay *= maxA / a; + } + + // 加速度を元の速度に足して、新速度とする + this._faceVX += ax; + this._faceVY += ay; + + // 目的の方向に近づいたとき、滑らかに減速するための処理 + // 設定された加速度で止まる事の出来る距離と速度の関係から + // 現在とりうる最高速度を計算し、それ以上の時は速度を落とす + // ※本来、人間は筋力で力(加速度)を調整できるため、より自由度が高いが、簡単な処理で済ませている + { + // 加速度、速度、距離の関係式。 + // 2 6 2 3 + // sqrt(a t + 16 a h t - 8 a h) - a t + // v = -------------------------------------- + // 2 + // 4 t - 2 + // (t=1) + // 時刻tは、あらかじめ加速度、速度を1/60(フレームレート、単位なし)で + // 考えているので、t=1として消してよい(※未検証) + + const maxV: number = + 0.5 * + (CubismMath.sqrt(maxA * maxA + 16.0 * maxA * d - 8.0 * maxA * d) - + maxA); + const curV: number = CubismMath.sqrt( + this._faceVX * this._faceVX + this._faceVY * this._faceVY + ); + + if (curV > maxV) { + // 現在の速度 > 最高速度のとき、最高速度まで減速 + this._faceVX *= maxV / curV; + this._faceVY *= maxV / curV; + } + } + + this._faceX += this._faceVX; + this._faceY += this._faceVY; + } + + /** + * X軸の顔の向きの値を取得 + * + * @return X軸の顔の向きの値(-1.0 ~ 1.0) + */ + public getX(): number { + return this._faceX; + } + + /** + * Y軸の顔の向きの値を取得 + * + * @return Y軸の顔の向きの値(-1.0 ~ 1.0) + */ + public getY(): number { + return this._faceY; + } + + /** + * 顔の向きの目標値を設定 + * + * @param x X軸の顔の向きの値(-1.0 ~ 1.0) + * @param y Y軸の顔の向きの値(-1.0 ~ 1.0) + */ + public set(x: number, y: number): void { + this._faceTargetX = x; + this._faceTargetY = y; + } + + private _faceTargetX: number; // 顔の向きのX目標値(この値に近づいていく) + private _faceTargetY: number; // 顔の向きのY目標値(この値に近づいていく) + private _faceX: number; // 顔の向きX(-1.0 ~ 1.0) + private _faceY: number; // 顔の向きY(-1.0 ~ 1.0) + private _faceVX: number; // 顔の向きの変化速度X + private _faceVY: number; // 顔の向きの変化速度Y + private _lastTimeSeconds: number; // 最後の実行時間[秒] + private _userTimeSeconds: number; // デルタ時間の積算値[秒] +} + +// Namespace definition for compatibility. +import * as $ from './cubismtargetpoint'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismTargetPoint = $.CubismTargetPoint; + export type CubismTargetPoint = $.CubismTargetPoint; +} diff --git a/avatar-h5-renderer/framework/src/math/cubismvector2.ts b/avatar-h5-renderer/framework/src/math/cubismvector2.ts new file mode 100644 index 0000000..449b766 --- /dev/null +++ b/avatar-h5-renderer/framework/src/math/cubismvector2.ts @@ -0,0 +1,172 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * 2次元ベクトル型 + * + * 2次元ベクトル型の機能を提供する。 + */ +export class CubismVector2 { + /** + * コンストラクタ + */ + public constructor( + public x?: number, + public y?: number + ) { + this.x = x == undefined ? 0.0 : x; + + this.y = y == undefined ? 0.0 : y; + } + + /** + * ベクトルの加算 + * + * @param vector2 加算するベクトル値 + * @return 加算結果 ベクトル値 + */ + public add(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x + vector2.x; + ret.y = this.y + vector2.y; + return ret; + } + + /** + * ベクトルの減算 + * + * @param vector2 減算するベクトル値 + * @return 減算結果 ベクトル値 + */ + public substract(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x - vector2.x; + ret.y = this.y - vector2.y; + return ret; + } + + /** + * ベクトルの乗算 + * + * @param vector2 乗算するベクトル値 + * @return 乗算結果 ベクトル値 + */ + public multiply(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x * vector2.x; + ret.y = this.y * vector2.y; + return ret; + } + + /** + * ベクトルの乗算(スカラー) + * + * @param scalar 乗算するスカラー値 + * @return 乗算結果 ベクトル値 + */ + public multiplyByScaler(scalar: number): CubismVector2 { + return this.multiply(new CubismVector2(scalar, scalar)); + } + + /** + * ベクトルの除算 + * + * @param vector2 除算するベクトル値 + * @return 除算結果 ベクトル値 + */ + public division(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x / vector2.x; + ret.y = this.y / vector2.y; + return ret; + } + + /** + * ベクトルの除算(スカラー) + * + * @param scalar 除算するスカラー値 + * @return 除算結果 ベクトル値 + */ + public divisionByScalar(scalar: number): CubismVector2 { + return this.division(new CubismVector2(scalar, scalar)); + } + + /** + * ベクトルの長さを取得する + * + * @return ベクトルの長さ + */ + public getLength(): number { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + + /** + * ベクトルの距離の取得 + * + * @param a 点 + * @return ベクトルの距離 + */ + public getDistanceWith(a: CubismVector2): number { + return Math.sqrt( + (this.x - a.x) * (this.x - a.x) + (this.y - a.y) * (this.y - a.y) + ); + } + + /** + * ドット積の計算 + * + * @param a 値 + * @return 結果 + */ + public dot(a: CubismVector2): number { + return this.x * a.x + this.y * a.y; + } + + /** + * 正規化の適用 + */ + public normalize(): void { + const length: number = Math.pow(this.x * this.x + this.y * this.y, 0.5); + + this.x = this.x / length; + this.y = this.y / length; + } + + /** + * 等しさの確認(等しいか?) + * + * 値が等しいか? + * + * @param rhs 確認する値 + * @return true 値は等しい + * @return false 値は等しくない + */ + public isEqual(rhs: CubismVector2): boolean { + return this.x == rhs.x && this.y == rhs.y; + } + + /** + * 等しさの確認(等しくないか?) + * + * 値が等しくないか? + * + * @param rhs 確認する値 + * @return true 値は等しくない + * @return false 値は等しい + */ + public isNotEqual(rhs: CubismVector2): boolean { + return !this.isEqual(rhs); + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismvector2'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismVector2 = $.CubismVector2; + export type CubismVector2 = $.CubismVector2; +} diff --git a/avatar-h5-renderer/framework/src/math/cubismviewmatrix.ts b/avatar-h5-renderer/framework/src/math/cubismviewmatrix.ts new file mode 100644 index 0000000..c81a05a --- /dev/null +++ b/avatar-h5-renderer/framework/src/math/cubismviewmatrix.ts @@ -0,0 +1,339 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMatrix44 } from './cubismmatrix44'; + +/** + * カメラの位置変更に使うと便利な4x4行列 + * + * カメラの位置変更に使うと便利な4x4行列のクラス。 + */ +export class CubismViewMatrix extends CubismMatrix44 { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._screenLeft = 0.0; + this._screenRight = 0.0; + this._screenTop = 0.0; + this._screenBottom = 0.0; + this._maxLeft = 0.0; + this._maxRight = 0.0; + this._maxTop = 0.0; + this._maxBottom = 0.0; + this._maxScale = 0.0; + this._minScale = 0.0; + } + + /** + * 移動を調整 + * + * @param x X軸の移動量 + * @param y Y軸の移動量 + */ + public adjustTranslate(x: number, y: number): void { + if (this._tr[0] * this._maxLeft + (this._tr[12] + x) > this._screenLeft) { + x = this._screenLeft - this._tr[0] * this._maxLeft - this._tr[12]; + } + + if (this._tr[0] * this._maxRight + (this._tr[12] + x) < this._screenRight) { + x = this._screenRight - this._tr[0] * this._maxRight - this._tr[12]; + } + + if (this._tr[5] * this._maxTop + (this._tr[13] + y) < this._screenTop) { + y = this._screenTop - this._tr[5] * this._maxTop - this._tr[13]; + } + + if ( + this._tr[5] * this._maxBottom + (this._tr[13] + y) > + this._screenBottom + ) { + y = this._screenBottom - this._tr[5] * this._maxBottom - this._tr[13]; + } + + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + x, + y, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 拡大率を調整 + * + * @param cx 拡大を行うX軸の中心位置 + * @param cy 拡大を行うY軸の中心位置 + * @param scale 拡大率 + */ + public adjustScale(cx: number, cy: number, scale: number): void { + const maxScale: number = this.getMaxScale(); + const minScale: number = this.getMinScale(); + + const targetScale = scale * this._tr[0]; + + if (targetScale < minScale) { + if (this._tr[0] > 0.0) { + scale = minScale / this._tr[0]; + } + } else if (targetScale > maxScale) { + if (this._tr[0] > 0.0) { + scale = maxScale / this._tr[0]; + } + } + + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + cx, + cy, + 0.0, + 1.0 + ]); + + const tr2: Float32Array = new Float32Array([ + scale, + 0.0, + 0.0, + 0.0, + 0.0, + scale, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]); + + const tr3: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + -cx, + -cy, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr3, this._tr, this._tr); + CubismMatrix44.multiply(tr2, this._tr, this._tr); + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * デバイスに対応する論理座養生の範囲の設定 + * + * @param left 左辺のX軸の位置 + * @param right 右辺のX軸の位置 + * @param bottom 下辺のY軸の位置 + * @param top 上辺のY軸の位置 + */ + public setScreenRect( + left: number, + right: number, + bottom: number, + top: number + ): void { + this._screenLeft = left; + this._screenRight = right; + this._screenBottom = bottom; + this._screenTop = top; + } + + /** + * デバイスに対応する論理座標上の移動可能範囲の設定 + * @param left 左辺のX軸の位置 + * @param right 右辺のX軸の位置 + * @param bottom 下辺のY軸の位置 + * @param top 上辺のY軸の位置 + */ + public setMaxScreenRect( + left: number, + right: number, + bottom: number, + top: number + ): void { + this._maxLeft = left; + this._maxRight = right; + this._maxTop = top; + this._maxBottom = bottom; + } + + /** + * 最大拡大率の設定 + * @param maxScale 最大拡大率 + */ + public setMaxScale(maxScale: number): void { + this._maxScale = maxScale; + } + + /** + * 最小拡大率の設定 + * @param minScale 最小拡大率 + */ + public setMinScale(minScale: number): void { + this._minScale = minScale; + } + + /** + * 最大拡大率の取得 + * @return 最大拡大率 + */ + public getMaxScale(): number { + return this._maxScale; + } + + /** + * 最小拡大率の取得 + * @return 最小拡大率 + */ + public getMinScale(): number { + return this._minScale; + } + + /** + * 拡大率が最大になっているかを確認する + * + * @return true 拡大率は最大 + * @return false 拡大率は最大ではない + */ + public isMaxScale(): boolean { + return this.getScaleX() >= this._maxScale; + } + + /** + * 拡大率が最小になっているかを確認する + * + * @return true 拡大率は最小 + * @return false 拡大率は最小ではない + */ + public isMinScale(): boolean { + return this.getScaleX() <= this._minScale; + } + + /** + * デバイスに対応する論理座標の左辺のX軸位置を取得する + * @return デバイスに対応する論理座標の左辺のX軸位置 + */ + public getScreenLeft(): number { + return this._screenLeft; + } + + /** + * デバイスに対応する論理座標の右辺のX軸位置を取得する + * @return デバイスに対応する論理座標の右辺のX軸位置 + */ + public getScreenRight(): number { + return this._screenRight; + } + + /** + * デバイスに対応する論理座標の下辺のY軸位置を取得する + * @return デバイスに対応する論理座標の下辺のY軸位置 + */ + public getScreenBottom(): number { + return this._screenBottom; + } + + /** + * デバイスに対応する論理座標の上辺のY軸位置を取得する + * @return デバイスに対応する論理座標の上辺のY軸位置 + */ + public getScreenTop(): number { + return this._screenTop; + } + + /** + * 左辺のX軸位置の最大値の取得 + * @return 左辺のX軸位置の最大値 + */ + public getMaxLeft(): number { + return this._maxLeft; + } + + /** + * 右辺のX軸位置の最大値の取得 + * @return 右辺のX軸位置の最大値 + */ + public getMaxRight(): number { + return this._maxRight; + } + + /** + * 下辺のY軸位置の最大値の取得 + * @return 下辺のY軸位置の最大値 + */ + public getMaxBottom(): number { + return this._maxBottom; + } + + /** + * 上辺のY軸位置の最大値の取得 + * @return 上辺のY軸位置の最大値 + */ + public getMaxTop(): number { + return this._maxTop; + } + + private _screenLeft: number; // デバイスに対応する論理座標上の範囲(左辺X軸位置) + private _screenRight: number; // デバイスに対応する論理座標上の範囲(右辺X軸位置) + private _screenTop: number; // デバイスに対応する論理座標上の範囲(上辺Y軸位置) + private _screenBottom: number; // デバイスに対応する論理座標上の範囲(下辺Y軸位置) + private _maxLeft: number; // 論理座標上の移動可能範囲(左辺X軸位置) + private _maxRight: number; // 論理座標上の移動可能範囲(右辺X軸位置) + private _maxTop: number; // 論理座標上の移動可能範囲(上辺Y軸位置) + private _maxBottom: number; // 論理座標上の移動可能範囲(下辺Y軸位置) + private _maxScale: number; // 拡大率の最大値 + private _minScale: number; // 拡大率の最小値 +} + +// Namespace definition for compatibility. +import * as $ from './cubismviewmatrix'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismViewMatrix = $.CubismViewMatrix; + export type CubismViewMatrix = $.CubismViewMatrix; +} diff --git a/avatar-h5-renderer/framework/src/model/cubismmoc.ts b/avatar-h5-renderer/framework/src/model/cubismmoc.ts new file mode 100644 index 0000000..94c3819 --- /dev/null +++ b/avatar-h5-renderer/framework/src/model/cubismmoc.ts @@ -0,0 +1,155 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CSM_ASSERT, CubismLogError } from '../utils/cubismdebug'; +import { CubismModel } from './cubismmodel'; + +/** + * Mocデータの管理 + * + * Mocデータの管理を行うクラス。 + */ +export class CubismMoc { + /** + * Mocデータの作成 + */ + public static create( + mocBytes: ArrayBuffer, + shouldCheckMocConsistency: boolean + ): CubismMoc { + let cubismMoc: CubismMoc = null; + + if (shouldCheckMocConsistency) { + // .moc3の整合性を確認 + const consistency = this.hasMocConsistency(mocBytes); + + if (!consistency) { + // 整合性が確認できなければ処理しない + CubismLogError(`Inconsistent MOC3.`); + return cubismMoc; + } + } + + const moc: Live2DCubismCore.Moc = + Live2DCubismCore.Moc.fromArrayBuffer(mocBytes); + + if (moc) { + cubismMoc = new CubismMoc(moc); + cubismMoc._mocVersion = + Live2DCubismCore.Version.csmGetMocVersion(mocBytes); + } + + return cubismMoc; + } + + /** + * Mocデータを削除 + * + * Mocデータを削除する + */ + public static delete(moc: CubismMoc): void { + moc._moc._release(); + moc._moc = null; + moc = null; + } + + /** + * モデルを作成する + * + * @return Mocデータから作成されたモデル + */ + createModel(): CubismModel { + let cubismModel: CubismModel = null; + + const model: Live2DCubismCore.Model = Live2DCubismCore.Model.fromMoc( + this._moc + ); + + if (model) { + cubismModel = new CubismModel(model); + cubismModel.initialize(); + + ++this._modelCount; + } + + return cubismModel; + } + + /** + * モデルを削除する + */ + deleteModel(model: CubismModel): void { + if (model != null) { + model.release(); + model = null; + --this._modelCount; + } + } + + /** + * コンストラクタ + */ + private constructor(moc: Live2DCubismCore.Moc) { + this._moc = moc; + this._modelCount = 0; + this._mocVersion = 0; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CSM_ASSERT(this._modelCount == 0); + + this._moc._release(); + this._moc = null; + } + + /** + * 最新の.moc3 Versionを取得 + */ + public getLatestMocVersion(): number { + return Live2DCubismCore.Version.csmGetLatestMocVersion(); + } + + /** + * 読み込んだモデルの.moc3 Versionを取得 + */ + public getMocVersion(): number { + return this._mocVersion; + } + + /** + * Mocファイルのbufferから.moc3 Versionを取得 + * @param mocBytes Mocファイルのバイト配列 + * @returns .moc3 Version番号 + */ + public static getMocVersionFromBuffer(mocBytes: ArrayBuffer): number { + return Live2DCubismCore.Version.csmGetMocVersion(mocBytes); + } + + /** + * .moc3 の整合性を検証する + */ + public static hasMocConsistency(mocBytes: ArrayBuffer): boolean { + const isConsistent = + Live2DCubismCore.Moc.prototype.hasMocConsistency(mocBytes); + return isConsistent === 1 ? true : false; + } + + _moc: Live2DCubismCore.Moc; // Mocデータ + _modelCount: number; // Mocデータから作られたモデルの個数 + _mocVersion: number; // 読み込んだモデルの.moc3 Version +} + +// Namespace definition for compatibility. +import * as $ from './cubismmoc'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMoc = $.CubismMoc; + export type CubismMoc = $.CubismMoc; +} diff --git a/avatar-h5-renderer/framework/src/model/cubismmodel.ts b/avatar-h5-renderer/framework/src/model/cubismmodel.ts new file mode 100644 index 0000000..e8b23f5 --- /dev/null +++ b/avatar-h5-renderer/framework/src/model/cubismmodel.ts @@ -0,0 +1,1930 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismMath } from '../math/cubismmath'; +import { + CubismBlendMode, + CubismTextureColor +} from '../rendering/cubismrenderer'; +import { CSM_ASSERT } from '../utils/cubismdebug'; +import { CubismModelMultiplyAndScreenColor } from './cubismmodelmultiplyandscreencolor'; + +export const NoParentIndex = -1; // 親が取得できない場合の値を表す定数 +export const NoOffscreenIndex = -1; // オフスクリーンが取得できない場合の値を表す定数 +/** + * カラーブレンドのタイプ + */ +export enum CubismColorBlend { + ColorBlend_None = -1, + ColorBlend_Normal = Live2DCubismCore.ColorBlendType_Normal, + ColorBlend_AddGlow = Live2DCubismCore.ColorBlendType_AddGlow, + ColorBlend_Add = Live2DCubismCore.ColorBlendType_Add, + ColorBlend_Darken = Live2DCubismCore.ColorBlendType_Darken, + ColorBlend_Multiply = Live2DCubismCore.ColorBlendType_Multiply, + ColorBlend_ColorBurn = Live2DCubismCore.ColorBlendType_ColorBurn, + ColorBlend_LinearBurn = Live2DCubismCore.ColorBlendType_LinearBurn, + ColorBlend_Lighten = Live2DCubismCore.ColorBlendType_Lighten, + ColorBlend_Screen = Live2DCubismCore.ColorBlendType_Screen, + ColorBlend_ColorDodge = Live2DCubismCore.ColorBlendType_ColorDodge, + ColorBlend_Overlay = Live2DCubismCore.ColorBlendType_Overlay, + ColorBlend_SoftLight = Live2DCubismCore.ColorBlendType_SoftLight, + ColorBlend_HardLight = Live2DCubismCore.ColorBlendType_HardLight, + ColorBlend_LinearLight = Live2DCubismCore.ColorBlendType_LinearLight, + ColorBlend_Hue = Live2DCubismCore.ColorBlendType_Hue, + ColorBlend_Color = Live2DCubismCore.ColorBlendType_Color, + // Cubism 5.2以前 + ColorBlend_AddCompatible = Live2DCubismCore.ColorBlendType_AddCompatible, + ColorBlend_MultiplyCompatible = Live2DCubismCore.ColorBlendType_MultiplyCompatible +} + +/** + * アルファブレンドのタイプ + */ +export enum CubismAlphaBlend { + AlphaBlend_None = -1, + AlphaBlend_Over, + AlphaBlend_Atop, + AlphaBlend_Out, + AlphaBlend_ConjointOver, + AlphaBlend_DisjointOver +} + +/** + * オブジェクトのタイプ + */ +export enum CubismModelObjectType { + CubismModelObjectType_Drawable = 0, + CubismModelObjectType_Parts = 1 +} + +/** + * Structure for managing the override of parameter repetition settings + */ +export class ParameterRepeatData { + /** + * Constructor + * + * @param isOverridden whether to be overriden + * @param isParameterRepeated override flag for settings + */ + public constructor( + isOverridden: boolean = false, + isParameterRepeated: boolean = false + ) { + this.isOverridden = isOverridden; + this.isParameterRepeated = isParameterRepeated; + } + + /** + * Whether to be overridden + */ + public isOverridden: boolean; + + /** + * Override flag for settings + */ + public isParameterRepeated: boolean; +} + +/** + * (deprecated) テクスチャのカリング設定を管理するための構造体 + */ +export class DrawableCullingData { + /** + * コンストラクタ + * + * @param isOverridden + * @param isCulling + */ + public constructor(isOverridden = false, isCulling = false) { + this.isOverridden = isOverridden; + this.isCulling = isCulling; + } + + public isOverridden: boolean; + public isCulling: boolean; + + get isOverwritten(): boolean { + return this.isOverridden; + } +} + +/** + * テクスチャのカリング設定を管理するための構造体 + */ +export class CullingData { + /** + * コンストラクタ + * + * @param isOverridden + * @param isCulling + */ + public constructor(isOverridden = false, isCulling = false) { + this.isOverridden = isOverridden; + this.isCulling = isCulling; + } + + public isOverridden: boolean; + public isCulling: boolean; +} + +/** + * パーツ子描画オブジェクト情報構造体 + */ +export class PartChildDrawObjects { + public drawableIndices: Array; + public offscreenIndices: Array; + + constructor( + drawableIndices: Array = new Array(), + offscreenIndices: Array = new Array() + ) { + this.drawableIndices = drawableIndices; + this.offscreenIndices = offscreenIndices; + } +} + +/** + * オブジェクト情報構造体 + */ +export class CubismModelObjectInfo { + public objectType: CubismModelObjectType; // オブジェクトのタイプ (Drawable / Parts) + public objectIndex: number; // オブジェクトインデックス + + constructor(objectIndex: number, objectType: CubismModelObjectType) { + this.objectIndex = objectIndex; + this.objectType = objectType; + } +} + +/** + * パーツ情報管理構造体 + */ +export class CubismModelPartInfo { + public objects: Array; + public childDrawObjects: PartChildDrawObjects; + + constructor( + objects: Array = new Array(), + childDrawObjects: PartChildDrawObjects = new PartChildDrawObjects() + ) { + this.objects = objects; + this.childDrawObjects = childDrawObjects; + } + + // 子オブジェクト数を返す関数 + public getChildObjectCount(): number { + return this.objects.length; + } +} + +/** + * モデル + * + * Mocデータから生成されるモデルのクラス。 + */ +export class CubismModel { + /** + * モデルのパラメータの更新 + */ + public update(): void { + // Update model + this._model.update(); + + this._model.drawables.resetDynamicFlags(); + } + + /** + * PixelsPerUnitを取得する + * @return PixelsPerUnit + */ + public getPixelsPerUnit(): number { + if (this._model == null) { + return 0.0; + } + + return this._model.canvasinfo.PixelsPerUnit; + } + + /** + * キャンバスの幅を取得する + */ + public getCanvasWidth(): number { + if (this._model == null) { + return 0.0; + } + + return ( + this._model.canvasinfo.CanvasWidth / this._model.canvasinfo.PixelsPerUnit + ); + } + + /** + * キャンバスの高さを取得する + */ + public getCanvasHeight(): number { + if (this._model == null) { + return 0.0; + } + + return ( + this._model.canvasinfo.CanvasHeight / this._model.canvasinfo.PixelsPerUnit + ); + } + + /** + * パラメータを保存する + */ + public saveParameters(): void { + const parameterCount: number = this._model.parameters.count; + const savedParameterCount: number = this._savedParameters.length; + + for (let i = 0; i < parameterCount; ++i) { + if (i < savedParameterCount) { + this._savedParameters[i] = this._parameterValues[i]; + } else { + this._savedParameters.push(this._parameterValues[i]); + } + } + } + + /** + * 乗算色・スクリーン色管理クラスを取得する + * + * @return CubismModelMultiplyAndScreenColorのインスタンス + */ + public getOverrideMultiplyAndScreenColor(): CubismModelMultiplyAndScreenColor { + return this._overrideMultiplyAndScreenColor; + } + + /** + * Checks whether parameter repetition is performed for the entire model. + * + * @return true if parameter repetition is performed for the entire model; otherwise returns false. + */ + public getOverrideFlagForModelParameterRepeat(): boolean { + return this._isOverriddenParameterRepeat; + } + + /** + * Sets whether parameter repetition is performed for the entire model. + * Use true to perform parameter repetition for the entire model, or false to not perform it. + */ + public setOverrideFlagForModelParameterRepeat(isRepeat: boolean): void { + this._isOverriddenParameterRepeat = isRepeat; + } + + /** + * Returns the flag indicating whether to override the parameter repeat. + * + * @param parameterIndex Parameter index + * + * @return true if the parameter repeat is overridden, false otherwise. + */ + public getOverrideFlagForParameterRepeat(parameterIndex: number): boolean { + return this._userParameterRepeatDataList[parameterIndex].isOverridden; + } + + /** + * Sets the flag indicating whether to override the parameter repeat. + * + * @param parameterIndex Parameter index + * @param value true if it is to be overridden; otherwise, false. + */ + public setOverrideFlagForParameterRepeat( + parameterIndex: number, + value: boolean + ): void { + this._userParameterRepeatDataList[parameterIndex].isOverridden = value; + } + + /** + * Returns the repeat flag. + * + * @param parameterIndex Parameter index + * + * @return true if repeating, false otherwise. + */ + public getRepeatFlagForParameterRepeat(parameterIndex: number): boolean { + return this._userParameterRepeatDataList[parameterIndex] + .isParameterRepeated; + } + + /** + * Sets the repeat flag. + * + * @param parameterIndex Parameter index + * @param value true to enable repeating, false otherwise. + */ + public setRepeatFlagForParameterRepeat( + parameterIndex: number, + value: boolean + ): void { + this._userParameterRepeatDataList[parameterIndex].isParameterRepeated = + value; + } + + /** + * Drawableのカリング情報を取得する。 + * + * @param drawableIndex Drawableのインデックス + * + * @return Drawableのカリング情報 + */ + public getDrawableCulling(drawableIndex: number): boolean { + if ( + this.getOverrideFlagForModelCullings() || + this.getOverrideFlagForDrawableCullings(drawableIndex) + ) { + return this._userDrawableCullings[drawableIndex].isCulling; + } + + const constantFlags = this._model.drawables.constantFlags; + return !Live2DCubismCore.Utils.hasIsDoubleSidedBit( + constantFlags[drawableIndex] + ); + } + + /** + * Drawableのカリング情報を設定する。 + * + * @param drawableIndex Drawableのインデックス + * @param isCulling カリング情報 + */ + public setDrawableCulling(drawableIndex: number, isCulling: boolean): void { + this._userDrawableCullings[drawableIndex].isCulling = isCulling; + } + + /** + * Offscreenのカリング情報を取得する。 + * + * @param offscreenIndex Offscreenのインデックス + * + * @return Offscreenのカリング情報 + */ + public getOffscreenCulling(offscreenIndex: number): boolean { + if ( + this.getOverrideFlagForModelCullings() || + this.getOverrideFlagForOffscreenCullings(offscreenIndex) + ) { + return this._userOffscreenCullings[offscreenIndex].isCulling; + } + + const constantFlags = this._model.offscreens.constantFlags; + return !Live2DCubismCore.Utils.hasIsDoubleSidedBit( + constantFlags[offscreenIndex] + ); + } + + /** + * Offscreenのカリング設定を設定する。 + * + * @param offscreenIndex Offscreenのインデックス + * @param isCulling カリング情報 + */ + public setOffscreenCulling(offscreenIndex: number, isCulling: boolean): void { + this._userOffscreenCullings[offscreenIndex].isCulling = isCulling; + } + + /** + * SDKからモデル全体のカリング設定を上書きするか。 + * + * @return true -> SDK上のカリング設定を使用 + * false -> モデルのカリング設定を使用 + */ + public getOverrideFlagForModelCullings(): boolean { + return this._isOverriddenCullings; + } + + /** + * SDKからモデル全体のカリング設定を上書きするかを設定する。 + * + * @param isOverriddenCullings SDK上のカリング設定を使うならtrue、モデルのカリング設定を使うならfalse + */ + public setOverrideFlagForModelCullings(isOverriddenCullings: boolean): void { + this._isOverriddenCullings = isOverriddenCullings; + } + + /** + * + * @param drawableIndex Drawableのインデックス + * @return true -> SDK上のカリング設定を使用 + * false -> モデルのカリング設定を使用 + */ + public getOverrideFlagForDrawableCullings(drawableIndex: number): boolean { + return this._userDrawableCullings[drawableIndex].isOverridden; + } + + /** + * @param offscreenIndex Offscreenのインデックス + * @return true -> SDK上のカリング設定を使用 + * false -> モデルのカリング設定を使用 + */ + public getOverrideFlagForOffscreenCullings(offscreenIndex: number): boolean { + return this._userOffscreenCullings[offscreenIndex].isOverridden; + } + + /** + * + * @param drawableIndex Drawableのインデックス + * @param isOverriddenCullings SDK上のカリング設定を使うならtrue、モデルのカリング設定を使うならfalse + */ + public setOverrideFlagForDrawableCullings( + drawableIndex: number, + isOverriddenCullings: boolean + ): void { + this._userDrawableCullings[drawableIndex].isOverridden = + isOverriddenCullings; + } + + /** + * モデルの不透明度を取得する + * + * @return 不透明度の値 + */ + public getModelOapcity(): number { + return this._modelOpacity; + } + + /** + * モデルの不透明度を設定する + * + * @param value 不透明度の値 + */ + public setModelOapcity(value: number) { + this._modelOpacity = value; + } + + /** + * モデルを取得 + */ + public getModel(): Live2DCubismCore.Model { + return this._model; + } + + /** + * パーツのインデックスを取得 + * @param partId パーツのID + * @return パーツのインデックス + */ + public getPartIndex(partId: CubismIdHandle): number { + let partIndex: number; + const partCount: number = this._model.parts.count; + + for (partIndex = 0; partIndex < partCount; ++partIndex) { + if (partId == this._partIds[partIndex]) { + return partIndex; + } + } + + // モデルに存在していない場合、非存在パーツIDリスト内にあるかを検索し、そのインデックスを返す + if (this._notExistPartId.has(partId)) { + return this._notExistPartId.get(partId); + } + + // 非存在パーツIDリストにない場合、新しく要素を追加する + partIndex = partCount + this._notExistPartId.size; + this._notExistPartId.set(partId, partIndex); + this._notExistPartOpacities.set(partIndex, null); + + return partIndex; + } + + /** + * パーツのIDを取得する。 + * + * @param partIndex 取得するパーツのインデックス + * @return パーツのID + */ + public getPartId(partIndex: number): CubismIdHandle { + const partId = this._model.parts.ids[partIndex]; + return CubismFramework.getIdManager().getId(partId); + } + + /** + * パーツの個数の取得 + * @return パーツの個数 + */ + public getPartCount(): number { + const partCount: number = this._model.parts.count; + return partCount; + } + + /** + * パーツのオフスクリーンインデックスの取得 + * @param partIndex パーツのインデックス + * @return オフスクリーンインデックスのリスト + */ + public getPartOffscreenIndices(): Int32Array { + const offscreenIndices = this._model.parts.offscreenIndices; + return offscreenIndices; + } + + /** + * パーツの親パーツインデックスのリストを取得 + * + * @return パーツの親パーツインデックスのリスト + */ + public getPartParentPartIndices(): Int32Array { + const parentIndices = this._model.parts.parentIndices; + return parentIndices; + } + + /** + * パーツの不透明度の設定(Index) + * @param partIndex パーツのインデックス + * @param opacity 不透明度 + */ + public setPartOpacityByIndex(partIndex: number, opacity: number): void { + if (this._notExistPartOpacities.has(partIndex)) { + this._notExistPartOpacities.set(partIndex, opacity); + return; + } + + // インデックスの範囲内検知 + CSM_ASSERT(0 <= partIndex && partIndex < this.getPartCount()); + + this._partOpacities[partIndex] = opacity; + } + + /** + * パーツの不透明度の設定(Id) + * @param partId パーツのID + * @param opacity パーツの不透明度 + */ + public setPartOpacityById(partId: CubismIdHandle, opacity: number): void { + // 高速化のためにPartIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const index: number = this.getPartIndex(partId); + + if (index < 0) { + return; // パーツがないのでスキップ + } + + this.setPartOpacityByIndex(index, opacity); + } + + /** + * パーツの不透明度の取得(index) + * @param partIndex パーツのインデックス + * @return パーツの不透明度 + */ + public getPartOpacityByIndex(partIndex: number): number { + if (this._notExistPartOpacities.has(partIndex)) { + // モデルに存在しないパーツIDの場合、非存在パーツリストから不透明度を返す。 + return this._notExistPartOpacities.get(partIndex); + } + + // インデックスの範囲内検知 + CSM_ASSERT(0 <= partIndex && partIndex < this.getPartCount()); + + return this._partOpacities[partIndex]; + } + + /** + * パーツの不透明度の取得(id) + * @param partId パーツのId + * @return パーツの不透明度 + */ + public getPartOpacityById(partId: CubismIdHandle): number { + // 高速化のためにPartIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const index: number = this.getPartIndex(partId); + + if (index < 0) { + return 0; // パーツが無いのでスキップ + } + + return this.getPartOpacityByIndex(index); + } + + /** + * パラメータのインデックスの取得 + * @param パラメータID + * @return パラメータのインデックス + */ + public getParameterIndex(parameterId: CubismIdHandle): number { + let parameterIndex: number; + const idCount: number = this._model.parameters.count; + + for (parameterIndex = 0; parameterIndex < idCount; ++parameterIndex) { + if (parameterId != this._parameterIds[parameterIndex]) { + continue; + } + + return parameterIndex; + } + + // モデルに存在していない場合、非存在パラメータIDリスト内を検索し、そのインデックスを返す + if (this._notExistParameterId.has(parameterId)) { + return this._notExistParameterId.get(parameterId); + } + + // 非存在パラメータIDリストにない場合新しく要素を追加する + parameterIndex = + this._model.parameters.count + this._notExistParameterId.size; + + this._notExistParameterId.set(parameterId, parameterIndex); + this._notExistParameterValues.set(parameterIndex, null); + + return parameterIndex; + } + + /** + * パラメータの個数の取得 + * @return パラメータの個数 + */ + public getParameterCount(): number { + return this._model.parameters.count; + } + + /** + * パラメータの種類の取得 + * @param parameterIndex パラメータのインデックス + * @return csmParameterType_Normal -> 通常のパラメータ + * csmParameterType_BlendShape -> ブレンドシェイプパラメータ + */ + public getParameterType( + parameterIndex: number + ): Live2DCubismCore.csmParameterType { + return this._model.parameters.types[parameterIndex]; + } + + /** + * パラメータの最大値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの最大値 + */ + public getParameterMaximumValue(parameterIndex: number): number { + return this._model.parameters.maximumValues[parameterIndex]; + } + + /** + * パラメータの最小値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの最小値 + */ + public getParameterMinimumValue(parameterIndex: number): number { + return this._model.parameters.minimumValues[parameterIndex]; + } + + /** + * パラメータのデフォルト値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータのデフォルト値 + */ + public getParameterDefaultValue(parameterIndex: number): number { + return this._model.parameters.defaultValues[parameterIndex]; + } + + /** + * 指定したパラメータindexのIDを取得 + * + * @param parameterIndex パラメータのインデックス + * @return パラメータID + */ + public getParameterId(parameterIndex: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._model.parameters.ids[parameterIndex] + ); + } + + /** + * パラメータの値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの値 + */ + public getParameterValueByIndex(parameterIndex: number): number { + if (this._notExistParameterValues.has(parameterIndex)) { + return this._notExistParameterValues.get(parameterIndex); + } + + // インデックスの範囲内検知 + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + return this._parameterValues[parameterIndex]; + } + + /** + * パラメータの値の取得 + * @param parameterId パラメータのID + * @return パラメータの値 + */ + public getParameterValueById(parameterId: CubismIdHandle): number { + // 高速化のためにparameterIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const parameterIndex: number = this.getParameterIndex(parameterId); + return this.getParameterValueByIndex(parameterIndex); + } + + /** + * パラメータの値の設定 + * @param parameterIndex パラメータのインデックス + * @param value パラメータの値 + * @param weight 重み + */ + public setParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + if (this._notExistParameterValues.has(parameterIndex)) { + this._notExistParameterValues.set( + parameterIndex, + weight == 1 + ? value + : this._notExistParameterValues.get(parameterIndex) * (1 - weight) + + value * weight + ); + + return; + } + + // インデックスの範囲内検知 + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + if (this.isRepeat(parameterIndex)) { + value = this.getParameterRepeatValue(parameterIndex, value); + } else { + value = this.getParameterClampValue(parameterIndex, value); + } + + this._parameterValues[parameterIndex] = + weight == 1 + ? value + : (this._parameterValues[parameterIndex] = + this._parameterValues[parameterIndex] * (1 - weight) + + value * weight); + } + + /** + * パラメータの値の設定 + * @param parameterId パラメータのID + * @param value パラメータの値 + * @param weight 重み + */ + public setParameterValueById( + parameterId: CubismIdHandle, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.setParameterValueByIndex(index, value, weight); + } + + /** + * パラメータの値の加算(index) + * @param parameterIndex パラメータインデックス + * @param value 加算する値 + * @param weight 重み + */ + public addParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + this.setParameterValueByIndex( + parameterIndex, + this.getParameterValueByIndex(parameterIndex) + value * weight + ); + } + + /** + * パラメータの値の加算(id) + * @param parameterId パラメータID + * @param value 加算する値 + * @param weight 重み + */ + public addParameterValueById( + parameterId: any, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.addParameterValueByIndex(index, value, weight); + } + + /** + * Gets whether the parameter has the repeat setting. + * + * @param parameterIndex Parameter index + * + * @return true if it is set, otherwise returns false. + */ + public isRepeat(parameterIndex: number): boolean { + if (this._notExistParameterValues.has(parameterIndex)) { + return false; + } + + // In-index range detection + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + let isRepeat: boolean; + + // Determines whether to perform parameter repeat processing + if ( + this._isOverriddenParameterRepeat || + this._userParameterRepeatDataList[parameterIndex].isOverridden + ) { + // Use repeat information set on the SDK side + isRepeat = + this._userParameterRepeatDataList[parameterIndex].isParameterRepeated; + } else { + // Use repeat information set in Editor + isRepeat = this._model.parameters.repeats[parameterIndex] != 0; + } + + return isRepeat; + } + + /** + * Returns the calculated result ensuring the value falls within the parameter's range. + * + * @param parameterIndex Parameter index + * @param value Parameter value + * + * @return a value that falls within the parameter’s range. If the parameter does not exist, returns it as is. + */ + public getParameterRepeatValue( + parameterIndex: number, + value: number + ): number { + if (this._notExistParameterValues.has(parameterIndex)) { + return value; + } + + // In-index range detection + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + const maxValue: number = + this._model.parameters.maximumValues[parameterIndex]; + const minValue: number = + this._model.parameters.minimumValues[parameterIndex]; + const valueSize: number = maxValue - minValue; + + if (maxValue < value) { + const overValue: number = CubismMath.mod(value - maxValue, valueSize); + if (!Number.isNaN(overValue)) { + value = minValue + overValue; + } else { + value = maxValue; + } + } + if (value < minValue) { + const overValue: number = CubismMath.mod(minValue - value, valueSize); + if (!Number.isNaN(overValue)) { + value = maxValue - overValue; + } else { + value = minValue; + } + } + + return value; + } + + /** + * Returns the result of clamping the value to ensure it falls within the parameter's range. + * + * @param parameterIndex Parameter index + * @param value Parameter value + * + * @return the clamped value. If the parameter does not exist, returns it as is. + */ + public getParameterClampValue(parameterIndex: number, value: number): number { + if (this._notExistParameterValues.has(parameterIndex)) { + return value; + } + + // In-index range detection + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + const maxValue: number = + this._model.parameters.maximumValues[parameterIndex]; + const minValue: number = + this._model.parameters.minimumValues[parameterIndex]; + + return CubismMath.clamp(value, minValue, maxValue); + } + + /** + * Returns the repeat of the parameter. + * + * @param parameterIndex Parameter index + * + * @return the raw data parameter repeat from the Cubism Core. + */ + public getParameterRepeats(parameterIndex: number): boolean { + return this._model.parameters.repeats[parameterIndex] != 0; + } + + /** + * パラメータの値の乗算 + * @param parameterId パラメータのID + * @param value 乗算する値 + * @param weight 重み + */ + public multiplyParameterValueById( + parameterId: CubismIdHandle, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.multiplyParameterValueByIndex(index, value, weight); + } + + /** + * パラメータの値の乗算 + * @param parameterIndex パラメータのインデックス + * @param value 乗算する値 + * @param weight 重み + */ + public multiplyParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + this.setParameterValueByIndex( + parameterIndex, + this.getParameterValueByIndex(parameterIndex) * + (1.0 + (value - 1.0) * weight) + ); + } + + /** + * Drawableのインデックスの取得 + * @param drawableId DrawableのID + * @return Drawableのインデックス + */ + public getDrawableIndex(drawableId: CubismIdHandle): number { + const drawableCount = this._model.drawables.count; + + for ( + let drawableIndex = 0; + drawableIndex < drawableCount; + ++drawableIndex + ) { + if (this._drawableIds[drawableIndex] == drawableId) { + return drawableIndex; + } + } + + return -1; + } + + /** + * Drawableの個数の取得 + * @return drawableの個数 + */ + public getDrawableCount(): number { + const drawableCount = this._model.drawables.count; + return drawableCount; + } + + /** + * DrawableのIDを取得する + * @param drawableIndex Drawableのインデックス + * @return drawableのID + */ + public getDrawableId(drawableIndex: number): CubismIdHandle { + const parameterIds: string[] = this._model.drawables.ids; + return CubismFramework.getIdManager().getId(parameterIds[drawableIndex]); + } + + /** + * Drawableの描画順リストの取得 + * @return Drawableの描画順リスト + */ + public getRenderOrders(): Int32Array { + const renderOrders: Int32Array = this._model.getRenderOrders(); + return renderOrders; + } + + /** + * Drawableのテクスチャインデックスの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのテクスチャインデックス + */ + public getDrawableTextureIndex(drawableIndex: number): number { + const textureIndices: Int32Array = this._model.drawables.textureIndices; + return textureIndices[drawableIndex]; + } + + /** + * DrawableのVertexPositionsの変化情報の取得 + * + * 直近のCubismModel.update関数でDrawableの頂点情報が変化したかを取得する。 + * + * @param drawableIndex Drawableのインデックス + * @return true Drawableの頂点情報が直近のCubismModel.update関数で変化した + * false Drawableの頂点情報が直近のCubismModel.update関数で変化していない + */ + public getDrawableDynamicFlagVertexPositionsDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasVertexPositionsDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの頂点インデックスの個数の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点インデックスの個数 + */ + public getDrawableVertexIndexCount(drawableIndex: number): number { + const indexCounts: Int32Array = this._model.drawables.indexCounts; + return indexCounts[drawableIndex]; + } + + /** + * Drawableの頂点の個数の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点の個数 + */ + public getDrawableVertexCount(drawableIndex: number): number { + const vertexCounts = this._model.drawables.vertexCounts; + return vertexCounts[drawableIndex]; + } + + /** + * Drawableの頂点リストの取得 + * @param drawableIndex drawableのインデックス + * @return drawableの頂点リスト + */ + public getDrawableVertices(drawableIndex: number): Float32Array { + return this.getDrawableVertexPositions(drawableIndex); + } + + /** + * Drawableの頂点インデックスリストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点インデックスリスト + */ + public getDrawableVertexIndices(drawableIndex: number): Uint16Array { + const indicesArray: Uint16Array[] = this._model.drawables.indices; + return indicesArray[drawableIndex]; + } + + /** + * Drawableの頂点リストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点リスト + */ + public getDrawableVertexPositions(drawableIndex: number): Float32Array { + const verticesArray: Float32Array[] = this._model.drawables.vertexPositions; + return verticesArray[drawableIndex]; + } + + /** + * Drawableの頂点のUVリストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点UVリスト + */ + public getDrawableVertexUvs(drawableIndex: number): Float32Array { + const uvsArray: Float32Array[] = this._model.drawables.vertexUvs; + return uvsArray[drawableIndex]; + } + + /** + * Drawableの不透明度の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの不透明度 + */ + public getDrawableOpacity(drawableIndex: number): number { + const opacities: Float32Array = this._model.drawables.opacities; + return opacities[drawableIndex]; + } + + /** + * Drawableの乗算色の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの乗算色(RGBA) + * スクリーン色はRGBAで取得されるが、Aは必ず0 + */ + public getDrawableMultiplyColor(drawableIndex: number): CubismTextureColor { + if (this._drawableMultiplyColors == null) { + this._drawableMultiplyColors = new Array( + this._model.drawables.count + ); + this._drawableMultiplyColors.fill(new CubismTextureColor()); + } + const multiplyColors: Float32Array = this._model.drawables.multiplyColors; + + const index = drawableIndex * 4; + this._drawableMultiplyColors[drawableIndex].r = multiplyColors[index]; + this._drawableMultiplyColors[drawableIndex].g = multiplyColors[index + 1]; + this._drawableMultiplyColors[drawableIndex].b = multiplyColors[index + 2]; + this._drawableMultiplyColors[drawableIndex].a = multiplyColors[index + 3]; + return this._drawableMultiplyColors[drawableIndex]; + } + + /** + * Drawableのスクリーン色の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのスクリーン色(RGBA) + * スクリーン色はRGBAで取得されるが、Aは必ず0 + */ + public getDrawableScreenColor(drawableIndex: number): CubismTextureColor { + if (this._drawableScreenColors == null) { + this._drawableScreenColors = new Array( + this._model.drawables.count + ); + this._drawableScreenColors.fill(new CubismTextureColor()); + } + const screenColors: Float32Array = this._model.drawables.screenColors; + + const index = drawableIndex * 4; + this._drawableScreenColors[drawableIndex].r = screenColors[index]; + this._drawableScreenColors[drawableIndex].g = screenColors[index + 1]; + this._drawableScreenColors[drawableIndex].b = screenColors[index + 2]; + this._drawableScreenColors[drawableIndex].a = screenColors[index + 3]; + return this._drawableScreenColors[drawableIndex]; + } + + /** + * Offscreenの乗算色の取得 + * @param offscreenIndex Offscreenのインデックス + * @return Offscreenの乗算色(RGBA) + * スクリーン色はRGBAで取得されるが、Aは必ず0 + */ + public getOffscreenMultiplyColor(offscreenIndex: number): CubismTextureColor { + if (this._offscreenMultiplyColors == null) { + this._offscreenMultiplyColors = new Array( + this._model.offscreens.count + ); + this._offscreenMultiplyColors.fill(new CubismTextureColor()); + } + const multiplyColors: Float32Array = this._model.offscreens.multiplyColors; + + const index = offscreenIndex * 4; + this._offscreenMultiplyColors[offscreenIndex].r = multiplyColors[index]; + this._offscreenMultiplyColors[offscreenIndex].g = multiplyColors[index + 1]; + this._offscreenMultiplyColors[offscreenIndex].b = multiplyColors[index + 2]; + this._offscreenMultiplyColors[offscreenIndex].a = multiplyColors[index + 3]; + return this._offscreenMultiplyColors[offscreenIndex]; + } + + /** + * Offscreenのスクリーン色の取得 + * @param offscreenIndex Offscreenのインデックス + * @return Offscreenのスクリーン色(RGBA) + * スクリーン色はRGBAで取得されるが、Aは必ず0 + */ + public getOffscreenScreenColor(offscreenIndex: number): CubismTextureColor { + if (this._offscreenScreenColors == null) { + this._offscreenScreenColors = new Array( + this._model.offscreens.count + ); + this._offscreenScreenColors.fill(new CubismTextureColor()); + } + const screenColors: Float32Array = this._model.offscreens.screenColors; + + const index = offscreenIndex * 4; + this._offscreenScreenColors[offscreenIndex].r = screenColors[index]; + this._offscreenScreenColors[offscreenIndex].g = screenColors[index + 1]; + this._offscreenScreenColors[offscreenIndex].b = screenColors[index + 2]; + this._offscreenScreenColors[offscreenIndex].a = screenColors[index + 3]; + return this._offscreenScreenColors[offscreenIndex]; + } + + /** + * Drawableの親パーツのインデックスの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの親パーツのインデックス + */ + public getDrawableParentPartIndex(drawableIndex: number): number { + return this._model.drawables.parentPartIndices[drawableIndex]; + } + + /** + * Drawableのブレンドモードを取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのブレンドモード + */ + public getDrawableBlendMode(drawableIndex: number): CubismBlendMode { + const constantFlags = this._model.drawables.constantFlags; + + return Live2DCubismCore.Utils.hasBlendAdditiveBit( + constantFlags[drawableIndex] + ) + ? CubismBlendMode.CubismBlendMode_Additive + : Live2DCubismCore.Utils.hasBlendMultiplicativeBit( + constantFlags[drawableIndex] + ) + ? CubismBlendMode.CubismBlendMode_Multiplicative + : CubismBlendMode.CubismBlendMode_Normal; + } + + /** + * Drawableのカラーブレンドの取得(Cubism 5.3 以降) + * + * @param drawableIndex Drawableのインデックス + * @return Drawableのカラーブレンド + */ + public getDrawableColorBlend(drawableIndex: number): CubismColorBlend { + // キャッシュ + if ( + this._drawableColorBlends[drawableIndex] == + CubismColorBlend.ColorBlend_None + ) { + this._drawableColorBlends[drawableIndex] = + this._model.drawables.blendModes[drawableIndex] & 0xff; + } + return this._drawableColorBlends[drawableIndex]; + } + + /** + * Drawableのアルファブレンドの取得(Cubism 5.3 以降) + * + * @param drawableIndex Drawableのインデックス + * @return Drawableのアルファブレンド + */ + public getDrawableAlphaBlend(drawableIndex: number): CubismAlphaBlend { + // キャッシュ + if ( + this._drawableAlphaBlends[drawableIndex] == + CubismAlphaBlend.AlphaBlend_None + ) { + this._drawableAlphaBlends[drawableIndex] = + (this._model.drawables.blendModes[drawableIndex] >> 8) & 0xff; + } + return this._drawableAlphaBlends[drawableIndex]; + } + + /** + * Drawableのマスクの反転使用の取得 + * + * Drawableのマスク使用時の反転設定を取得する。 + * マスクを使用しない場合は無視される。 + * + * @param drawableIndex Drawableのインデックス + * @return Drawableの反転設定 + */ + public getDrawableInvertedMaskBit(drawableIndex: number): boolean { + const constantFlags: Uint8Array = this._model.drawables.constantFlags; + + return Live2DCubismCore.Utils.hasIsInvertedMaskBit( + constantFlags[drawableIndex] + ); + } + + /** + * Drawableのクリッピングマスクリストの取得 + * @return Drawableのクリッピングマスクリスト + */ + public getDrawableMasks(): Int32Array[] { + const masks: Int32Array[] = this._model.drawables.masks; + return masks; + } + + /** + * Drawableのクリッピングマスクの個数リストの取得 + * @return Drawableのクリッピングマスクの個数リスト + */ + public getDrawableMaskCounts(): Int32Array { + const maskCounts: Int32Array = this._model.drawables.maskCounts; + return maskCounts; + } + + /** + * クリッピングマスクの使用状態 + * + * @return true クリッピングマスクを使用している + * @return false クリッピングマスクを使用していない + */ + public isUsingMasking(): boolean { + for (let d = 0; d < this._model.drawables.count; ++d) { + if (this._model.drawables.maskCounts[d] <= 0) { + continue; + } + return true; + } + return false; + } + + /** + * Offscreenでクリッピングマスクを使用しているかどうかを取得 + * + * @return true クリッピングマスクをオフスクリーンで使用している + */ + public isUsingMaskingForOffscreen(): boolean { + for (let d = 0; d < this.getOffscreenCount(); ++d) { + if (this._model.offscreens.maskCounts[d] <= 0) { + continue; + } + return true; + } + return false; + } + + /** + * Drawableの表示情報を取得する + * + * @param drawableIndex Drawableのインデックス + * @return true Drawableが表示 + * @return false Drawableが非表示 + */ + public getDrawableDynamicFlagIsVisible(drawableIndex: number): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasIsVisibleBit(dynamicFlags[drawableIndex]); + } + + /** + * DrawableのDrawOrderの変化情報の取得 + * + * 直近のCubismModel.update関数でdrawableのdrawOrderが変化したかを取得する。 + * drawOrderはartMesh上で指定する0から1000の情報 + * @param drawableIndex drawableのインデックス + * @return true drawableの不透明度が直近のCubismModel.update関数で変化した + * @return false drawableの不透明度が直近のCubismModel.update関数で変化している + */ + public getDrawableDynamicFlagVisibilityDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasVisibilityDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの不透明度の変化情報の取得 + * + * 直近のCubismModel.update関数でdrawableの不透明度が変化したかを取得する。 + * + * @param drawableIndex drawableのインデックス + * @return true Drawableの不透明度が直近のCubismModel.update関数で変化した + * @return false Drawableの不透明度が直近のCubismModel.update関数で変化してない + */ + public getDrawableDynamicFlagOpacityDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasOpacityDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの描画順序の変化情報の取得 + * + * 直近のCubismModel.update関数でDrawableの描画の順序が変化したかを取得する。 + * + * @param drawableIndex Drawableのインデックス + * @return true Drawableの描画の順序が直近のCubismModel.update関数で変化した + * @return false Drawableの描画の順序が直近のCubismModel.update関数で変化してない + */ + public getDrawableDynamicFlagRenderOrderDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasRenderOrderDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの乗算色・スクリーン色の変化情報の取得 + * + * 直近のCubismModel.update関数でDrawableの乗算色・スクリーン色が変化したかを取得する。 + * + * @param drawableIndex Drawableのインデックス + * @return true Drawableの乗算色・スクリーン色が直近のCubismModel.update関数で変化した + * @return false Drawableの乗算色・スクリーン色が直近のCubismModel.update関数で変化してない + */ + public getDrawableDynamicFlagBlendColorDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasBlendColorDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * オフスクリーンの個数を取得する + * @return オフスクリーンの個数 + */ + public getOffscreenCount(): number { + return this._model.offscreens.count; + } + + /** + * Offscreenのカラーブレンドの取得(Cubism 5.3 以降) + * + * @param offscreenIndex Offscreenのインデックス + * @return Offscreenのカラーブレンド + */ + public getOffscreenColorBlend(offscreenIndex: number): CubismColorBlend { + // キャッシュ + if ( + this._offscreenColorBlends[offscreenIndex] == + CubismColorBlend.ColorBlend_None + ) { + this._offscreenColorBlends[offscreenIndex] = + this._model.offscreens.blendModes[offscreenIndex] & 0xff; + } + return this._offscreenColorBlends[offscreenIndex]; + } + + /** + * Offscreenのアルファブレンドの取得(Cubism 5.3 以降) + * + * @param offscreenIndex Offscreenのインデックス + * @return Offscreenのアルファブレンド + */ + public getOffscreenAlphaBlend(offscreenIndex: number): CubismAlphaBlend { + // キャッシュ + if ( + this._offscreenAlphaBlends[offscreenIndex] == + CubismAlphaBlend.AlphaBlend_None + ) { + this._offscreenAlphaBlends[offscreenIndex] = + (this._model.offscreens.blendModes[offscreenIndex] >> 8) & 0xff; + } + return this._offscreenAlphaBlends[offscreenIndex]; + } + + /** + * オフスクリーンのオーナーインデックス配列を取得する + * @return オフスクリーンのオーナーインデックス配列 + */ + public getOffscreenOwnerIndices(): Int32Array { + return this._model.offscreens.ownerIndices; + } + + /** + * オフスクリーンの不透明度を取得 + * @param offscreenIndex オフスクリーンのインデックス + * @return 不透明度 + */ + public getOffscreenOpacity(offscreenIndex: number): number { + if (offscreenIndex < 0 || offscreenIndex >= this._model.offscreens.count) { + return 1.0; // オフスクリーンが無いのでスキップ + } + + return this._model.offscreens.opacities[offscreenIndex]; + } + + /** + * オフスクリーンのクリッピングマスクリストの取得 + * @return オフスクリーンのクリッピングマスクリスト + */ + public getOffscreenMasks(): Int32Array[] { + return this._model.offscreens.masks; + } + + /** + * オフスクリーンのクリッピングマスクの個数リストの取得 + * @return オフスクリーンのクリッピングマスクの個数リスト + */ + public getOffscreenMaskCounts(): Int32Array { + return this._model.offscreens.maskCounts; + } + + /** + * オフスクリーンのマスク反転設定を取得する + * @param offscreenIndex オフスクリーンのインデックス + * @return オフスクリーンのマスク反転設定 + */ + public getOffscreenInvertedMask(offscreenIndex: number): boolean { + const constantFlags: Uint8Array = this._model.offscreens.constantFlags; + // Live2DCubismCore.Utils.hasIsInvertedMaskBit を利用 + return Live2DCubismCore.Utils.hasIsInvertedMaskBit( + constantFlags[offscreenIndex] + ); + } + + /** + * ブレンドモード使用判定 + * @return ブレンドモードを使用しているか + */ + public isBlendModeEnabled(): boolean { + return this._isBlendModeEnabled; + } + + /** + * 保存されたパラメータの読み込み + */ + public loadParameters(): void { + let parameterCount: number = this._model.parameters.count; + const savedParameterCount: number = this._savedParameters.length; + + if (parameterCount > savedParameterCount) { + parameterCount = savedParameterCount; + } + + for (let i = 0; i < parameterCount; ++i) { + this._parameterValues[i] = this._savedParameters[i]; + } + } + + /** + * 初期化する + */ + public initialize(): void { + CSM_ASSERT(this._model); + + this._parameterValues = this._model.parameters.values; + this._partOpacities = this._model.parts.opacities; + this._offscreenOpacities = this._model.offscreens.opacities; + + this._parameterMaximumValues = this._model.parameters.maximumValues; + this._parameterMinimumValues = this._model.parameters.minimumValues; + + { + const parameterIds: string[] = this._model.parameters.ids; + const parameterCount: number = this._model.parameters.count; + + this._parameterIds.length = parameterCount; + this._userParameterRepeatDataList.length = parameterCount; + for (let i = 0; i < parameterCount; ++i) { + this._parameterIds[i] = CubismFramework.getIdManager().getId( + parameterIds[i] + ); + this._userParameterRepeatDataList[i] = new ParameterRepeatData( + false, + false + ); + } + } + + const partCount: number = this._model.parts.count; + { + const partIds: string[] = this._model.parts.ids; + + this._partIds.length = partCount; + for (let i = 0; i < partCount; ++i) { + this._partIds[i] = CubismFramework.getIdManager().getId(partIds[i]); + } + } + + { + const drawableIds: string[] = this._model.drawables.ids; + const drawableCount: number = this._model.drawables.count; + + // Drawableカリング設定 + this._userDrawableCullings.length = drawableCount; + const userCulling: CullingData = new CullingData(false, false); + + // Offscreenカリング設定 + this._userOffscreenCullings.length = this._model.offscreens.count; + const userOffscreenCulling: CullingData = new CullingData(false, false); + + // Drawables + { + for (let i = 0; i < drawableCount; ++i) { + this._drawableIds.push( + CubismFramework.getIdManager().getId(drawableIds[i]) + ); + + this._userDrawableCullings[i] = userCulling; + } + } + + // Offscreens + { + for (let i = 0; i < this._model.offscreens.count; ++i) { + this._userOffscreenCullings[i] = userOffscreenCulling; + } + } + + // blendMode + // オフスクリーンが存在するか、DrawableのブレンドモードでColorBlend、AlphaBlendを使用するのであればブレンドモードを有効にする。 + if (this.getOffscreenCount() > 0) { + this._isBlendModeEnabled = true; + } else { + const blendModes = this._model.drawables.blendModes; + for (let i = 0; i < drawableCount; ++i) { + const colorBlendType = this.getDrawableColorBlend(i); + const alphaBlendType = this.getDrawableAlphaBlend(i); + + // NormalOver、AddCompatible、MultiplyCompatible以外であればブレンドモードを有効にする。 + if ( + !( + colorBlendType == CubismColorBlend.ColorBlend_Normal && + alphaBlendType == CubismAlphaBlend.AlphaBlend_Over + ) && + colorBlendType != CubismColorBlend.ColorBlend_AddCompatible && + colorBlendType != CubismColorBlend.ColorBlend_MultiplyCompatible + ) { + this._isBlendModeEnabled = true; + break; + } + } + } + + this.setupPartsHierarchy(); + + // CubismModelMultiplyAndScreenColorの初期化 + const offscreenCount = this.getOffscreenCount(); + this._overrideMultiplyAndScreenColor.initialize( + partCount, + drawableCount, + offscreenCount + ); + } + } + + /** + * パーツ階層構造を取得する + * @return パーツ階層構造の配列 + */ + public getPartsHierarchy(): Array { + return this._partsHierarchy; + } + + /** + * パーツ階層構造をセットアップする + */ + public setupPartsHierarchy(): void { + this._partsHierarchy.length = 0; + + // すべてのパーツのパーツ情報管理構造体を作成 + const partCount = this.getPartCount(); + this._partsHierarchy.length = partCount; + for (let i = 0; i < partCount; ++i) { + const partInfo = new CubismModelPartInfo(); + this._partsHierarchy[i] = partInfo; + } + + // Partごとに親パーツを取得し、親パーツの子objectリストに追加する + for (let i = 0; i < partCount; ++i) { + const parentPartIndex = this.getPartParentPartIndices()[i]; + + if (parentPartIndex === NoParentIndex) { + continue; + } + + for ( + let partIndex = 0; + partIndex < this._partsHierarchy.length; + ++partIndex + ) { + if (partIndex === parentPartIndex) { + const objectInfo = new CubismModelObjectInfo( + i, + CubismModelObjectType.CubismModelObjectType_Parts + ); + this._partsHierarchy[partIndex].objects.push(objectInfo); + break; + } + } + } + + // Drawableごとに親パーツを取得し、親パーツの子objectリストに追加する + const drawableCount = this.getDrawableCount(); + for (let i = 0; i < drawableCount; ++i) { + const parentPartIndex = this.getDrawableParentPartIndex(i); + + if (parentPartIndex === NoParentIndex) { + continue; + } + + for ( + let partIndex = 0; + partIndex < this._partsHierarchy.length; + ++partIndex + ) { + if (partIndex === parentPartIndex) { + const objectInfo = new CubismModelObjectInfo( + i, + CubismModelObjectType.CubismModelObjectType_Drawable + ); + this._partsHierarchy[partIndex].objects.push(objectInfo); + break; + } + } + } + + // パーツ子描画オブジェクト情報構造体を作成していく + for (let i = 0; i < this._partsHierarchy.length; ++i) { + // パーツ管理構造体を取得 + this.getPartChildDrawObjects(i); + } + } + + /** + * 指定したパーツの子描画オブジェクト情報を取得・構築する + * @param partInfoIndex パーツ情報のインデックス + * @return PartChildDrawObjects + */ + public getPartChildDrawObjects(partInfoIndex: number): PartChildDrawObjects { + if (this._partsHierarchy[partInfoIndex].getChildObjectCount() < 1) { + // 子オブジェクトがない場合 + return this._partsHierarchy[partInfoIndex].childDrawObjects; + } + + const childDrawObjects = + this._partsHierarchy[partInfoIndex].childDrawObjects; + + // 既にchildDrawObjectsが処理されている場合はスキップ + if ( + childDrawObjects.drawableIndices.length !== 0 || + childDrawObjects.offscreenIndices.length !== 0 + ) { + return childDrawObjects; + } + + const objects = this._partsHierarchy[partInfoIndex].objects; + + for (let i = 0; i < objects.length; ++i) { + const obj = objects[i]; + + if ( + obj.objectType === CubismModelObjectType.CubismModelObjectType_Parts + ) { + // 子のパーツの場合、再帰的に子objectsを取得 + this.getPartChildDrawObjects(obj.objectIndex); + + // 子パーツの子Drawable、Offscreenを取得 + const childToChildDrawObjects = + this._partsHierarchy[obj.objectIndex].childDrawObjects; + + childDrawObjects.drawableIndices.push( + ...childToChildDrawObjects.drawableIndices + ); + childDrawObjects.offscreenIndices.push( + ...childToChildDrawObjects.offscreenIndices + ); + + // Offscreenの確認 + const offscreenIndices = this.getOffscreenIndices(); + const offscreenIndex = offscreenIndices + ? offscreenIndices[obj.objectIndex] + : NoOffscreenIndex; + if (offscreenIndex !== NoOffscreenIndex) { + childDrawObjects.offscreenIndices.push(offscreenIndex); + } + } else if ( + obj.objectType === CubismModelObjectType.CubismModelObjectType_Drawable + ) { + // Drawableの場合、パーツの子Drawableに追加 + childDrawObjects.drawableIndices.push(obj.objectIndex); + } + } + + return childDrawObjects; + } + + /** + * パーツのオフスクリーンインデックス配列を取得 + * @return Int32Array offscreenIndices + */ + private getOffscreenIndices(): Int32Array { + // _model.parts.offscreenIndices が存在する場合のみ返す + return this._model.parts.offscreenIndices; + } + + /** + * コンストラクタ + * @param model モデル + */ + public constructor(model: Live2DCubismCore.Model) { + this._model = model; + this._parameterValues = null; + this._parameterMaximumValues = null; + this._parameterMinimumValues = null; + this._partOpacities = null; + this._offscreenOpacities = null; + this._savedParameters = new Array(); + this._parameterIds = new Array(); + this._drawableIds = new Array(); + this._partIds = new Array(); + this._isOverriddenParameterRepeat = true; + this._isOverriddenCullings = false; + this._modelOpacity = 1.0; + + // CubismModelMultiplyAndScreenColorのインスタンスを作成 + this._overrideMultiplyAndScreenColor = + new CubismModelMultiplyAndScreenColor(this); + + this._isBlendModeEnabled = false; + this._drawableColorBlends = null; + this._drawableAlphaBlends = null; + this._offscreenColorBlends = null; + this._offscreenAlphaBlends = null; + this._drawableMultiplyColors = null; + this._drawableScreenColors = null; + this._offscreenMultiplyColors = null; + this._offscreenScreenColors = null; + + this._userParameterRepeatDataList = new Array(); + this._userDrawableCullings = new Array(); + this._userOffscreenCullings = new Array(); + this._partsHierarchy = new Array(); + + this._notExistPartId = new Map(); + this._notExistParameterId = new Map(); + this._notExistParameterValues = new Map(); + this._notExistPartOpacities = new Map(); + + // Drawableのカラーブレンドとアルファブレンドの初期化 + this._drawableColorBlends = new Array( + model.drawables.count + ).fill(CubismColorBlend.ColorBlend_None); + this._drawableAlphaBlends = new Array( + model.drawables.count + ).fill(CubismAlphaBlend.AlphaBlend_None); + + // Offscreenのカラーブレンドとアルファブレンドの初期化 + this._offscreenColorBlends = new Array( + model.offscreens.count + ).fill(CubismColorBlend.ColorBlend_None); + this._offscreenAlphaBlends = new Array( + model.offscreens.count + ).fill(CubismAlphaBlend.AlphaBlend_None); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._model.release(); + this._model = null; + + this._drawableColorBlends = null; + this._drawableAlphaBlends = null; + this._offscreenColorBlends = null; + this._offscreenAlphaBlends = null; + + this._drawableMultiplyColors = null; + this._drawableScreenColors = null; + this._offscreenMultiplyColors = null; + this._offscreenScreenColors = null; + } + + private _notExistPartOpacities: Map; // 存在していないパーツの不透明度のリスト + private _notExistPartId: Map; // 存在していないパーツIDのリスト + + private _notExistParameterValues: Map; // 存在していないパラメータの値のリスト + private _notExistParameterId: Map; // 存在していないパラメータIDのリスト + + private _savedParameters: Array; // 保存されたパラメータ + + /** + * Flag to determine whether to override model-wide parameter repeats on the SDK + */ + private _isOverriddenParameterRepeat: boolean; + + private _overrideMultiplyAndScreenColor: CubismModelMultiplyAndScreenColor; // 乗算色・スクリーン色の管理クラス + + /** + * List to manage ParameterRepeat and Override flag to be set for each Parameter + */ + private _userParameterRepeatDataList: Array; + private _partsHierarchy: Array; // Partの親子構造 + + private _model: Live2DCubismCore.Model; // モデル + + private _parameterValues: Float32Array; // パラメータの値のリスト + private _parameterMaximumValues: Float32Array; // パラメータの最大値のリスト + private _parameterMinimumValues: Float32Array; // パラメータの最小値のリスト + + private _partOpacities: Float32Array; // パーツの不透明度のリスト + private _offscreenOpacities: Float32Array; // オフスクリーンの不透明度のリスト + + private _modelOpacity: number; // モデルの不透明度 + + private _parameterIds: Array; + private _partIds: Array; + private _drawableIds: Array; + + private _isOverriddenCullings: boolean; // モデルのカリング設定をすべて上書きするか? + private _userDrawableCullings: Array; // カリング設定の配列 + private _userOffscreenCullings: Array; // オフスクリーンのカリング設定を使用するか? + + private _isBlendModeEnabled: boolean; // ブレンドモードを使用しているか + + private _drawableColorBlends: CubismColorBlend[]; // Drawableのカラーブレンドの配列 + private _drawableAlphaBlends: CubismAlphaBlend[]; // Drawableのアルファブレンドの配列 + private _offscreenColorBlends: CubismColorBlend[]; // Offscreen のカラーブレンドの配列 + private _offscreenAlphaBlends: CubismAlphaBlend[]; // Offscreen のアルファブレンドの配列 + + private _drawableMultiplyColors: CubismTextureColor[]; // Drawableの乗算色の配列 + private _drawableScreenColors: CubismTextureColor[]; // Drawableのスクリーン色の配列 + private _offscreenMultiplyColors: CubismTextureColor[]; // Offscreenの乗算色の配列 + private _offscreenScreenColors: CubismTextureColor[]; // Offscreenのスクリーン色の配列 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodel'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModel = $.CubismModel; + export type CubismModel = $.CubismModel; +} diff --git a/avatar-h5-renderer/framework/src/model/cubismmodelmultiplyandscreencolor.ts b/avatar-h5-renderer/framework/src/model/cubismmodelmultiplyandscreencolor.ts new file mode 100644 index 0000000..135ffae --- /dev/null +++ b/avatar-h5-renderer/framework/src/model/cubismmodelmultiplyandscreencolor.ts @@ -0,0 +1,1066 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismTextureColor } from '../rendering/cubismrenderer'; +import { CubismModelObjectType, NoOffscreenIndex } from './cubismmodel'; +import { CubismLogWarning } from '../utils/cubismdebug'; + +/** + * SDK側から与えられた描画オブジェクトの乗算色・スクリーン色上書きフラグと + * その色を保持する構造体 + */ +export class ColorData { + constructor( + isOverridden = false, + color: CubismTextureColor = new CubismTextureColor() + ) { + this.isOverridden = isOverridden; + this.color = color; + } + + public isOverridden: boolean; + public color: CubismTextureColor; +} + +/** + * Handling multiply and screen colors of the model. + */ +export class CubismModelMultiplyAndScreenColor { + private _model: any; // CubismModel + private _isOverriddenModelMultiplyColors: boolean; + private _isOverriddenModelScreenColors: boolean; + private _userPartScreenColors: Array; + private _userPartMultiplyColors: Array; + private _userDrawableScreenColors: Array; + private _userDrawableMultiplyColors: Array; + private _userOffscreenScreenColors: Array; + private _userOffscreenMultiplyColors: Array; + + /** + * Constructor. + * + * @param model cubism model. + */ + public constructor(model: any) { + this._model = model; + this._isOverriddenModelMultiplyColors = false; + this._isOverriddenModelScreenColors = false; + this._userPartScreenColors = []; + this._userPartMultiplyColors = []; + this._userDrawableScreenColors = []; + this._userDrawableMultiplyColors = []; + this._userOffscreenScreenColors = []; + this._userOffscreenMultiplyColors = []; + } + + /** + * Initialization for using multiply and screen colors. + * + * @param partCount number of parts. + * @param drawableCount number of drawables. + * @param offscreenCount number of offscreen. + */ + public initialize( + partCount: number, + drawableCount: number, + offscreenCount: number + ): void { + // 乗算色の初期値 + const userMultiplyColor = new ColorData( + false, + new CubismTextureColor(1.0, 1.0, 1.0, 1.0) + ); + + // スクリーン色の初期値 + const userScreenColor = new ColorData( + false, + new CubismTextureColor(0.0, 0.0, 0.0, 1.0) + ); + + // Part + this._userPartMultiplyColors = new Array(partCount); + this._userPartScreenColors = new Array(partCount); + for (let i = 0; i < partCount; i++) { + this._userPartMultiplyColors[i] = new ColorData( + userMultiplyColor.isOverridden, + new CubismTextureColor( + userMultiplyColor.color.r, + userMultiplyColor.color.g, + userMultiplyColor.color.b, + userMultiplyColor.color.a + ) + ); + this._userPartScreenColors[i] = new ColorData( + userScreenColor.isOverridden, + new CubismTextureColor( + userScreenColor.color.r, + userScreenColor.color.g, + userScreenColor.color.b, + userScreenColor.color.a + ) + ); + } + + // Drawable + this._userDrawableMultiplyColors = new Array(drawableCount); + this._userDrawableScreenColors = new Array(drawableCount); + for (let i = 0; i < drawableCount; i++) { + this._userDrawableMultiplyColors[i] = new ColorData( + userMultiplyColor.isOverridden, + new CubismTextureColor( + userMultiplyColor.color.r, + userMultiplyColor.color.g, + userMultiplyColor.color.b, + userMultiplyColor.color.a + ) + ); + this._userDrawableScreenColors[i] = new ColorData( + userScreenColor.isOverridden, + new CubismTextureColor( + userScreenColor.color.r, + userScreenColor.color.g, + userScreenColor.color.b, + userScreenColor.color.a + ) + ); + } + + // Offscreen + this._userOffscreenMultiplyColors = new Array(offscreenCount); + this._userOffscreenScreenColors = new Array(offscreenCount); + for (let i = 0; i < offscreenCount; i++) { + this._userOffscreenMultiplyColors[i] = new ColorData( + userMultiplyColor.isOverridden, + new CubismTextureColor( + userMultiplyColor.color.r, + userMultiplyColor.color.g, + userMultiplyColor.color.b, + userMultiplyColor.color.a + ) + ); + this._userOffscreenScreenColors[i] = new ColorData( + userScreenColor.isOverridden, + new CubismTextureColor( + userScreenColor.color.r, + userScreenColor.color.g, + userScreenColor.color.b, + userScreenColor.color.a + ) + ); + } + } + + /** + * Outputs a warning message for index out of range errors. + * + * @param functionName Name of the calling function + * @param index The invalid index value + * @param maxIndex The maximum valid index (length - 1) + */ + private warnIndexOutOfRange( + functionName: string, + index: number, + maxIndex: number + ): void { + CubismLogWarning( + `${functionName}: index is out of range. index=${index}, valid range=[0, ${maxIndex}].` + ); + } + + /** + * Validates if the given part index is within valid range. + * + * @param index Part index to validate + * @param functionName Name of the calling function for error reporting + * @return true if the index is valid; otherwise false + */ + private isValidPartIndex(index: number, functionName: string): boolean { + if (index < 0 || index >= this._model.getPartCount()) { + this.warnIndexOutOfRange( + functionName, + index, + this._model.getPartCount() - 1 + ); + return false; + } + return true; + } + + /** + * Validates if the given drawable index is within valid range. + * + * @param index Drawable index to validate + * @param functionName Name of the calling function for error reporting + * @return true if the index is valid; otherwise false + */ + private isValidDrawableIndex(index: number, functionName: string): boolean { + if (index < 0 || index >= this._model.getDrawableCount()) { + this.warnIndexOutOfRange( + functionName, + index, + this._model.getDrawableCount() - 1 + ); + return false; + } + return true; + } + + /** + * Validates if the given offscreen index is within valid range. + * + * @param index Offscreen index to validate + * @param functionName Name of the calling function for error reporting + * @return true if the index is valid; otherwise false + */ + private isValidOffscreenIndex(index: number, functionName: string): boolean { + if (index < 0 || index >= this._model.getOffscreenCount()) { + this.warnIndexOutOfRange( + functionName, + index, + this._model.getOffscreenCount() - 1 + ); + return false; + } + return true; + } + + /** + * Sets the flag indicating whether the color set at runtime is used as the multiply color for the entire model during rendering. + * + * @param value true if the color set at runtime is to be used; otherwise false. + */ + public setMultiplyColorEnabled(value: boolean): void { + this._isOverriddenModelMultiplyColors = value; + } + + /** + * Returns the flag indicating whether the color set at runtime is used as the multiply color for the entire model during rendering. + * + * @return true if the color set at runtime is used; otherwise false. + */ + public getMultiplyColorEnabled(): boolean { + return this._isOverriddenModelMultiplyColors; + } + + /** + * Sets the flag indicating whether the color set at runtime is used as the screen color for the entire model during rendering. + * + * @param value true if the color set at runtime is to be used; otherwise false. + */ + public setScreenColorEnabled(value: boolean): void { + this._isOverriddenModelScreenColors = value; + } + + /** + * Returns the flag indicating whether the color set at runtime is used as the screen color for the entire model during rendering. + * + * @return true if the color set at runtime is used; otherwise false. + */ + public getScreenColorEnabled(): boolean { + return this._isOverriddenModelScreenColors; + } + + /** + * Sets whether the part multiply color is overridden by the SDK. + * Use true to use the color information from the SDK, or false to use the color information from the model. + * + * @param partIndex Part index + * @param value true enable override, false to disable + */ + public setPartMultiplyColorEnabled(partIndex: number, value: boolean): void { + if (!this.isValidPartIndex(partIndex, 'setPartMultiplyColorEnabled')) { + return; + } + this.setPartColorEnabled( + partIndex, + value, + this._userPartMultiplyColors, + this._userDrawableMultiplyColors, + this._userOffscreenMultiplyColors + ); + } + + /** + * Checks whether the part multiply color is overridden by the SDK. + * + * @param partIndex Part index + * + * @return true if the color information from the SDK is used; otherwise false. + */ + public getPartMultiplyColorEnabled(partIndex: number): boolean { + if (!this.isValidPartIndex(partIndex, 'getPartMultiplyColorEnabled')) { + return false; + } + return this._userPartMultiplyColors[partIndex].isOverridden; + } + + /** + * Sets whether the part screen color is overridden by the SDK. + * Use true to use the color information from the SDK, or false to use the color information from the model. + * + * @param partIndex Part index + * @param value true enable override, false to disable + */ + public setPartScreenColorEnabled(partIndex: number, value: boolean): void { + if (!this.isValidPartIndex(partIndex, 'setPartScreenColorEnabled')) { + return; + } + this.setPartColorEnabled( + partIndex, + value, + this._userPartScreenColors, + this._userDrawableScreenColors, + this._userOffscreenScreenColors + ); + } + + /** + * Checks whether the part screen color is overridden by the SDK. + * + * @param partIndex Part index + * + * @return true if the color information from the SDK is used; otherwise false. + */ + public getPartScreenColorEnabled(partIndex: number): boolean { + if (!this.isValidPartIndex(partIndex, 'getPartScreenColorEnabled')) { + return false; + } + return this._userPartScreenColors[partIndex].isOverridden; + } + + /** + * Sets the multiply color of the part. + * + * @param partIndex Part index + * @param color Multiply color to be set (CubismTextureColor) + */ + public setPartMultiplyColorByTextureColor( + partIndex: number, + color: CubismTextureColor + ): void { + if ( + !this.isValidPartIndex(partIndex, 'setPartMultiplyColorByTextureColor') + ) { + return; + } + this.setPartMultiplyColorByRGBA( + partIndex, + color.r, + color.g, + color.b, + color.a + ); + } + + /** + * Sets the multiply color of the part. + * + * @param partIndex Part index + * @param r Red value of the multiply color to be set + * @param g Green value of the multiply color to be set + * @param b Blue value of the multiply color to be set + * @param a Alpha value of the multiply color to be set + */ + public setPartMultiplyColorByRGBA( + partIndex: number, + r: number, + g: number, + b: number, + a: number = 1.0 + ): void { + if (!this.isValidPartIndex(partIndex, 'setPartMultiplyColorByRGBA')) { + return; + } + this.setPartColor( + partIndex, + r, + g, + b, + a, + this._userPartMultiplyColors, + this._userDrawableMultiplyColors, + this._userOffscreenMultiplyColors + ); + } + + /** + * Returns the multiply color of the part. + * + * @param partIndex Part index + * + * @return Multiply color (CubismTextureColor) + */ + public getPartMultiplyColor(partIndex: number): CubismTextureColor { + if (!this.isValidPartIndex(partIndex, 'getPartMultiplyColor')) { + return new CubismTextureColor(1.0, 1.0, 1.0, 1.0); + } + return this._userPartMultiplyColors[partIndex].color; + } + + /** + * Sets the screen color of the part. + * + * @param partIndex Part index + * @param color Screen color to be set (CubismTextureColor) + */ + public setPartScreenColorByTextureColor( + partIndex: number, + color: CubismTextureColor + ): void { + if (!this.isValidPartIndex(partIndex, 'setPartScreenColorByTextureColor')) { + return; + } + this.setPartScreenColorByRGBA( + partIndex, + color.r, + color.g, + color.b, + color.a + ); + } + + /** + * Sets the screen color of the part. + * + * @param partIndex Part index + * @param r Red value of the screen color to be set + * @param g Green value of the screen color to be set + * @param b Blue value of the screen color to be set + * @param a Alpha value of the screen color to be set + */ + public setPartScreenColorByRGBA( + partIndex: number, + r: number, + g: number, + b: number, + a: number = 1.0 + ): void { + if (!this.isValidPartIndex(partIndex, 'setPartScreenColorByRGBA')) { + return; + } + this.setPartColor( + partIndex, + r, + g, + b, + a, + this._userPartScreenColors, + this._userDrawableScreenColors, + this._userOffscreenScreenColors + ); + } + + /** + * Returns the screen color of the part. + * + * @param partIndex Part index + * + * @return Screen color (CubismTextureColor) + */ + public getPartScreenColor(partIndex: number): CubismTextureColor { + if (!this.isValidPartIndex(partIndex, 'getPartScreenColor')) { + return new CubismTextureColor(0.0, 0.0, 0.0, 1.0); + } + return this._userPartScreenColors[partIndex].color; + } + + /** + * Sets the flag indicating whether the color set at runtime is used as the multiply color for the drawable during rendering. + * + * @param drawableIndex Drawable index + * @param value true if the color set at runtime is to be used; otherwise false. + */ + public setDrawableMultiplyColorEnabled( + drawableIndex: number, + value: boolean + ): void { + if ( + !this.isValidDrawableIndex( + drawableIndex, + 'setDrawableMultiplyColorEnabled' + ) + ) { + return; + } + this._userDrawableMultiplyColors[drawableIndex].isOverridden = value; + } + + /** + * Returns the flag indicating whether the color set at runtime is used as the multiply color for the drawable during rendering. + * + * @param drawableIndex Drawable index + * + * @return true if the color set at runtime is used; otherwise false. + */ + public getDrawableMultiplyColorEnabled(drawableIndex: number): boolean { + if ( + !this.isValidDrawableIndex( + drawableIndex, + 'getDrawableMultiplyColorEnabled' + ) + ) { + return false; + } + return this._userDrawableMultiplyColors[drawableIndex].isOverridden; + } + + /** + * Sets the flag indicating whether the color set at runtime is used as the screen color for the drawable during rendering. + * + * @param drawableIndex Drawable index + * @param value true if the color set at runtime is to be used; otherwise false. + */ + public setDrawableScreenColorEnabled( + drawableIndex: number, + value: boolean + ): void { + if ( + !this.isValidDrawableIndex(drawableIndex, 'setDrawableScreenColorEnabled') + ) { + return; + } + this._userDrawableScreenColors[drawableIndex].isOverridden = value; + } + + /** + * Returns the flag indicating whether the color set at runtime is used as the screen color for the drawable during rendering. + * + * @param drawableIndex Drawable index + * + * @return true if the color set at runtime is used; otherwise false. + */ + public getDrawableScreenColorEnabled(drawableIndex: number): boolean { + if ( + !this.isValidDrawableIndex(drawableIndex, 'getDrawableScreenColorEnabled') + ) { + return false; + } + return this._userDrawableScreenColors[drawableIndex].isOverridden; + } + + /** + * Sets the multiply color of the drawable. + * + * @param drawableIndex Drawable index + * @param color Multiply color to be set (CubismTextureColor) + */ + public setDrawableMultiplyColorByTextureColor( + drawableIndex: number, + color: CubismTextureColor + ): void { + if ( + !this.isValidDrawableIndex( + drawableIndex, + 'setDrawableMultiplyColorByTextureColor' + ) + ) { + return; + } + this.setDrawableMultiplyColorByRGBA( + drawableIndex, + color.r, + color.g, + color.b, + color.a + ); + } + + /** + * Sets the multiply color of the drawable. + * + * @param drawableIndex Drawable index + * @param r Red value of the multiply color to be set + * @param g Green value of the multiply color to be set + * @param b Blue value of the multiply color to be set + * @param a Alpha value of the multiply color to be set + */ + public setDrawableMultiplyColorByRGBA( + drawableIndex: number, + r: number, + g: number, + b: number, + a: number = 1.0 + ): void { + if ( + !this.isValidDrawableIndex( + drawableIndex, + 'setDrawableMultiplyColorByRGBA' + ) + ) { + return; + } + this._userDrawableMultiplyColors[drawableIndex].color.r = r; + this._userDrawableMultiplyColors[drawableIndex].color.g = g; + this._userDrawableMultiplyColors[drawableIndex].color.b = b; + this._userDrawableMultiplyColors[drawableIndex].color.a = a; + } + + /** + * Returns the multiply color from the list of drawables. + * + * @param drawableIndex Drawable index + * + * @return Multiply color (CubismTextureColor) + */ + public getDrawableMultiplyColor(drawableIndex: number): CubismTextureColor { + if (!this.isValidDrawableIndex(drawableIndex, 'getDrawableMultiplyColor')) { + return new CubismTextureColor(1.0, 1.0, 1.0, 1.0); + } + if ( + this.getMultiplyColorEnabled() || + this.getDrawableMultiplyColorEnabled(drawableIndex) + ) { + return this._userDrawableMultiplyColors[drawableIndex].color; + } + return this._model.getDrawableMultiplyColor(drawableIndex); + } + + /** + * Sets the screen color of the drawable. + * + * @param drawableIndex Drawable index + * @param color Screen color to be set (CubismTextureColor) + */ + public setDrawableScreenColorByTextureColor( + drawableIndex: number, + color: CubismTextureColor + ): void { + if ( + !this.isValidDrawableIndex( + drawableIndex, + 'setDrawableScreenColorByTextureColor' + ) + ) { + return; + } + this.setDrawableScreenColorByRGBA( + drawableIndex, + color.r, + color.g, + color.b, + color.a + ); + } + + /** + * Sets the screen color of the drawable. + * + * @param drawableIndex Drawable index + * @param r Red value of the screen color to be set + * @param g Green value of the screen color to be set + * @param b Blue value of the screen color to be set + * @param a Alpha value of the screen color to be set + */ + public setDrawableScreenColorByRGBA( + drawableIndex: number, + r: number, + g: number, + b: number, + a: number = 1.0 + ): void { + if ( + !this.isValidDrawableIndex(drawableIndex, 'setDrawableScreenColorByRGBA') + ) { + return; + } + this._userDrawableScreenColors[drawableIndex].color.r = r; + this._userDrawableScreenColors[drawableIndex].color.g = g; + this._userDrawableScreenColors[drawableIndex].color.b = b; + this._userDrawableScreenColors[drawableIndex].color.a = a; + } + + /** + * Returns the screen color from the list of drawables. + * + * @param drawableIndex Drawable index + * + * @return Screen color (CubismTextureColor) + */ + public getDrawableScreenColor(drawableIndex: number): CubismTextureColor { + if (!this.isValidDrawableIndex(drawableIndex, 'getDrawableScreenColor')) { + return new CubismTextureColor(0.0, 0.0, 0.0, 1.0); + } + if ( + this.getScreenColorEnabled() || + this.getDrawableScreenColorEnabled(drawableIndex) + ) { + return this._userDrawableScreenColors[drawableIndex].color; + } + return this._model.getDrawableScreenColor(drawableIndex); + } + + /** + * Sets whether the offscreen multiply color is overridden by the SDK. + * Use true to use the color information from the SDK, or false to use the color information from the model. + * + * @param offscreenIndex Offscreen index + * @param value true enable override, false to disable + */ + public setOffscreenMultiplyColorEnabled( + offscreenIndex: number, + value: boolean + ): void { + if ( + !this.isValidOffscreenIndex( + offscreenIndex, + 'setOffscreenMultiplyColorEnabled' + ) + ) { + return; + } + this._userOffscreenMultiplyColors[offscreenIndex].isOverridden = value; + } + + /** + * Checks whether the offscreen multiply color is overridden by the SDK. + * + * @param offscreenIndex Offscreen index + * + * @return true if the color information from the SDK is used; otherwise false. + */ + public getOffscreenMultiplyColorEnabled(offscreenIndex: number): boolean { + if ( + !this.isValidOffscreenIndex( + offscreenIndex, + 'getOffscreenMultiplyColorEnabled' + ) + ) { + return false; + } + return this._userOffscreenMultiplyColors[offscreenIndex].isOverridden; + } + + /** + * Sets whether the offscreen screen color is overridden by the SDK. + * Use true to use the color information from the SDK, or false to use the color information from the model. + * + * @param offscreenIndex Offscreen index + * @param value true enable override, false to disable + */ + public setOffscreenScreenColorEnabled( + offscreenIndex: number, + value: boolean + ): void { + if ( + !this.isValidOffscreenIndex( + offscreenIndex, + 'setOffscreenScreenColorEnabled' + ) + ) { + return; + } + this._userOffscreenScreenColors[offscreenIndex].isOverridden = value; + } + + /** + * Checks whether the offscreen screen color is overridden by the SDK. + * + * @param offscreenIndex Offscreen index + * + * @return true if the color information from the SDK is used; otherwise false. + */ + public getOffscreenScreenColorEnabled(offscreenIndex: number): boolean { + if ( + !this.isValidOffscreenIndex( + offscreenIndex, + 'getOffscreenScreenColorEnabled' + ) + ) { + return false; + } + return this._userOffscreenScreenColors[offscreenIndex].isOverridden; + } + + /** + * Sets the multiply color of the offscreen. + * + * @param offscreenIndex Offsscreen index + * @param color Multiply color to be set (CubismTextureColor) + */ + public setOffscreenMultiplyColorByTextureColor( + offscreenIndex: number, + color: CubismTextureColor + ): void { + if ( + !this.isValidOffscreenIndex( + offscreenIndex, + 'setOffscreenMultiplyColorByTextureColor' + ) + ) { + return; + } + this.setOffscreenMultiplyColorByRGBA( + offscreenIndex, + color.r, + color.g, + color.b, + color.a + ); + } + + /** + * Sets the multiply color of the offscreen. + * + * @param offscreenIndex Offsscreen index + * @param r Red value of the multiply color to be set + * @param g Green value of the multiply color to be set + * @param b Blue value of the multiply color to be set + * @param a Alpha value of the multiply color to be set + */ + public setOffscreenMultiplyColorByRGBA( + offscreenIndex: number, + r: number, + g: number, + b: number, + a: number = 1.0 + ): void { + if ( + !this.isValidOffscreenIndex( + offscreenIndex, + 'setOffscreenMultiplyColorByRGBA' + ) + ) { + return; + } + this._userOffscreenMultiplyColors[offscreenIndex].color.r = r; + this._userOffscreenMultiplyColors[offscreenIndex].color.g = g; + this._userOffscreenMultiplyColors[offscreenIndex].color.b = b; + this._userOffscreenMultiplyColors[offscreenIndex].color.a = a; + } + + /** + * Returns the multiply color from the list of offscreen. + * + * @param offscreenIndex Offsscreen index + * + * @return Multiply color (CubismTextureColor) + */ + public getOffscreenMultiplyColor(offscreenIndex: number): CubismTextureColor { + if ( + !this.isValidOffscreenIndex(offscreenIndex, 'getOffscreenMultiplyColor') + ) { + return new CubismTextureColor(1.0, 1.0, 1.0, 1.0); // Default offscreen multiply color + } + if ( + this.getMultiplyColorEnabled() || + this.getOffscreenMultiplyColorEnabled(offscreenIndex) + ) { + return this._userOffscreenMultiplyColors[offscreenIndex].color; + } + return this._model.getOffscreenMultiplyColor(offscreenIndex); + } + + /** + * Sets the screen color of the offscreen. + * + * @param offscreenIndex Offsscreen index + * @param color Screen color to be set (CubismTextureColor) + */ + public setOffscreenScreenColorByTextureColor( + offscreenIndex: number, + color: CubismTextureColor + ): void { + if ( + !this.isValidOffscreenIndex( + offscreenIndex, + 'setOffscreenScreenColorByTextureColor' + ) + ) { + return; + } + this.setOffscreenScreenColorByRGBA( + offscreenIndex, + color.r, + color.g, + color.b, + color.a + ); + } + + /** + * Sets the screen color of the offscreen. + * + * @param offscreenIndex Offsscreen index + * @param r Red value of the screen color to be set + * @param g Green value of the screen color to be set + * @param b Blue value of the screen color to be set + * @param a Alpha value of the screen color to be set + */ + public setOffscreenScreenColorByRGBA( + offscreenIndex: number, + r: number, + g: number, + b: number, + a: number = 1.0 + ): void { + if ( + !this.isValidOffscreenIndex( + offscreenIndex, + 'setOffscreenScreenColorByRGBA' + ) + ) { + return; + } + this._userOffscreenScreenColors[offscreenIndex].color.r = r; + this._userOffscreenScreenColors[offscreenIndex].color.g = g; + this._userOffscreenScreenColors[offscreenIndex].color.b = b; + this._userOffscreenScreenColors[offscreenIndex].color.a = a; + } + + /** + * Returns the screen color from the list of offscreen. + * + * @param offscreenIndex Offsscreen index + * + * @return Screen color (CubismTextureColor) + */ + public getOffscreenScreenColor(offscreenIndex: number): CubismTextureColor { + if ( + !this.isValidOffscreenIndex(offscreenIndex, 'getOffscreenScreenColor') + ) { + return new CubismTextureColor(0.0, 0.0, 0.0, 1.0); // Default offscreen screen color + } + if ( + this.getScreenColorEnabled() || + this.getOffscreenScreenColorEnabled(offscreenIndex) + ) { + return this._userOffscreenScreenColors[offscreenIndex].color; + } + return this._model.getOffscreenScreenColor(offscreenIndex); + } + + /** + * Sets the part color with hierarchical propagation (internal method) + */ + private setPartColor( + partIndex: number, + r: number, + g: number, + b: number, + a: number, + partColors: Array, + drawableColors: Array, + offscreenColors: Array + ): void { + partColors[partIndex].color.r = r; + partColors[partIndex].color.g = g; + partColors[partIndex].color.b = b; + partColors[partIndex].color.a = a; + + if (partColors[partIndex].isOverridden) { + const offscreenIndices = this._model.getPartOffscreenIndices(); + const offscreenIndex = offscreenIndices[partIndex]; + if (offscreenIndex == NoOffscreenIndex) { + // If no offscreen buffer is attached, the effect is applied to the children. + const partsHierarchy = this._model.getPartsHierarchy(); + if (partsHierarchy && partsHierarchy[partIndex]) { + for (let i = 0; i < partsHierarchy[partIndex].objects.length; ++i) { + const objectInfo = partsHierarchy[partIndex].objects[i]; + if ( + objectInfo.objectType === + CubismModelObjectType.CubismModelObjectType_Drawable + ) { + const drawableIndex = objectInfo.objectIndex; + drawableColors[drawableIndex].color.r = r; + drawableColors[drawableIndex].color.g = g; + drawableColors[drawableIndex].color.b = b; + drawableColors[drawableIndex].color.a = a; + } else { + const childPartIndex = objectInfo.objectIndex; + this.setPartColor( + childPartIndex, + r, + g, + b, + a, + partColors, + drawableColors, + offscreenColors + ); + } + } + } + } else { + // If an offscreen buffer is attached, only that offscreen buffer is affected. + offscreenColors[offscreenIndex].color.r = r; + offscreenColors[offscreenIndex].color.g = g; + offscreenColors[offscreenIndex].color.b = b; + offscreenColors[offscreenIndex].color.a = a; + } + } + } + + /** + * Sets the part color enabled flag with hierarchical propagation (internal method) + */ + private setPartColorEnabled( + partIndex: number, + value: boolean, + partColors: Array, + drawableColors: Array, + offscreenColors: Array + ): void { + partColors[partIndex].isOverridden = value; + + const offscreenIndices = this._model.getPartOffscreenIndices(); + const offscreenIndex = offscreenIndices[partIndex]; + if (offscreenIndex == NoOffscreenIndex) { + // If no offscreen buffer is attached, the effect is applied to the children. + const partsHierarchy = this._model.getPartsHierarchy(); + if (partsHierarchy && partsHierarchy[partIndex]) { + for (let i = 0; i < partsHierarchy[partIndex].objects.length; ++i) { + const objectInfo = partsHierarchy[partIndex].objects[i]; + if ( + objectInfo.objectType === + CubismModelObjectType.CubismModelObjectType_Drawable + ) { + const drawableIndex = objectInfo.objectIndex; + drawableColors[drawableIndex].isOverridden = value; + if (value) { + drawableColors[drawableIndex].color.r = + partColors[partIndex].color.r; + drawableColors[drawableIndex].color.g = + partColors[partIndex].color.g; + drawableColors[drawableIndex].color.b = + partColors[partIndex].color.b; + drawableColors[drawableIndex].color.a = + partColors[partIndex].color.a; + } + } else { + const childPartIndex = objectInfo.objectIndex; + if (value) { + partColors[childPartIndex].color.r = + partColors[partIndex].color.r; + partColors[childPartIndex].color.g = + partColors[partIndex].color.g; + partColors[childPartIndex].color.b = + partColors[partIndex].color.b; + partColors[childPartIndex].color.a = + partColors[partIndex].color.a; + } + this.setPartColorEnabled( + childPartIndex, + value, + partColors, + drawableColors, + offscreenColors + ); + } + } + } + } else { + // If an offscreen buffer is attached, only that offscreen buffer is affected. + offscreenColors[offscreenIndex].isOverridden = value; + if (value) { + offscreenColors[offscreenIndex].color.r = partColors[partIndex].color.r; + offscreenColors[offscreenIndex].color.g = partColors[partIndex].color.g; + offscreenColors[offscreenIndex].color.b = partColors[partIndex].color.b; + offscreenColors[offscreenIndex].color.a = partColors[partIndex].color.a; + } + } + } +} diff --git a/avatar-h5-renderer/framework/src/model/cubismmodeluserdata.ts b/avatar-h5-renderer/framework/src/model/cubismmodeluserdata.ts new file mode 100644 index 0000000..f8acc05 --- /dev/null +++ b/avatar-h5-renderer/framework/src/model/cubismmodeluserdata.ts @@ -0,0 +1,141 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismModelUserDataJson } from './cubismmodeluserdatajson'; + +const ArtMesh = 'ArtMesh'; + +/** + * ユーザーデータインターフェース + * + * Jsonから読み込んだユーザーデータを記録しておくための構造体 + */ +export class CubismModelUserDataNode { + targetType: CubismIdHandle; // ユーザーデータターゲットタイプ + targetId: CubismIdHandle; // ユーザーデータターゲットのID + value: string; // ユーザーデータ +} + +/** + * ユーザデータの管理クラス + * + * ユーザデータをロード、管理、検索インターフェイス、解放までを行う。 + */ +export class CubismModelUserData { + /** + * インスタンスの作成 + * + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create(buffer: ArrayBuffer, size: number): CubismModelUserData { + const ret: CubismModelUserData = new CubismModelUserData(); + + ret.parseUserData(buffer, size); + + return ret; + } + + /** + * インスタンスを破棄する + * + * @param modelUserData 破棄するインスタンス + */ + public static delete(modelUserData: CubismModelUserData): void { + if (modelUserData != null) { + modelUserData.release(); + modelUserData = null; + } + } + + /** + * ArtMeshのユーザーデータのリストの取得 + * + * @return ユーザーデータリスト + */ + public getArtMeshUserDatas(): Array { + return this._artMeshUserDataNode; + } + + /** + * userdata3.jsonのパース + * + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public parseUserData(buffer: ArrayBuffer, size: number): void { + let json: CubismModelUserDataJson = new CubismModelUserDataJson( + buffer, + size + ); + if (!json) { + json.release(); + json = void 0; + return; + } + + const typeOfArtMesh = CubismFramework.getIdManager().getId(ArtMesh); + const nodeCount: number = json.getUserDataCount(); + + let dstIndex = this._userDataNodes.length; + this._userDataNodes.length = nodeCount; + for (let i = 0; i < nodeCount; i++) { + const addNode: CubismModelUserDataNode = new CubismModelUserDataNode(); + + addNode.targetId = json.getUserDataId(i); + addNode.targetType = CubismFramework.getIdManager().getId( + json.getUserDataTargetType(i) + ); + addNode.value = json.getUserDataValue(i); + this._userDataNodes[dstIndex++] = addNode; + + if (addNode.targetType == typeOfArtMesh) { + this._artMeshUserDataNode.push(addNode); + } + } + + json.release(); + json = void 0; + } + + /** + * コンストラクタ + */ + public constructor() { + this._userDataNodes = new Array(); + this._artMeshUserDataNode = new Array(); + } + + /** + * デストラクタ相当の処理 + * + * ユーザーデータ構造体配列を解放する + */ + public release(): void { + for (let i = 0; i < this._userDataNodes.length; ++i) { + this._userDataNodes[i] = null; + } + + this._userDataNodes = null; + } + + private _userDataNodes: Array; // ユーザーデータ構造体配列 + private _artMeshUserDataNode: Array; // 閲覧リストの保持 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodeluserdata'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModelUserData = $.CubismModelUserData; + export type CubismModelUserData = $.CubismModelUserData; + export const CubismModelUserDataNode = $.CubismModelUserDataNode; + export type CubismModelUserDataNode = $.CubismModelUserDataNode; +} diff --git a/avatar-h5-renderer/framework/src/model/cubismmodeluserdatajson.ts b/avatar-h5-renderer/framework/src/model/cubismmodeluserdatajson.ts new file mode 100644 index 0000000..a691a70 --- /dev/null +++ b/avatar-h5-renderer/framework/src/model/cubismmodeluserdatajson.ts @@ -0,0 +1,117 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismJson } from '../utils/cubismjson'; + +const Meta = 'Meta'; +const UserDataCount = 'UserDataCount'; +const TotalUserDataSize = 'TotalUserDataSize'; +const UserData = 'UserData'; +const Target = 'Target'; +const Id = 'Id'; +const Value = 'Value'; + +export class CubismModelUserDataJson { + /** + * コンストラクタ + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * ユーザーデータ個数の取得 + * @return ユーザーデータの個数 + */ + public getUserDataCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(UserDataCount) + .toInt(); + } + + /** + * ユーザーデータ総文字列数の取得 + * + * @return ユーザーデータ総文字列数 + */ + public getTotalUserDataSize(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalUserDataSize) + .toInt(); + } + + /** + * ユーザーデータのタイプの取得 + * + * @return ユーザーデータのタイプ + */ + public getUserDataTargetType(i: number): string { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Target) + .getRawString(); + } + + /** + * ユーザーデータのターゲットIDの取得 + * + * @param i インデックス + * @return ユーザーデータターゲットID + */ + public getUserDataId(i: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * ユーザーデータの文字列の取得 + * + * @param i インデックス + * @return ユーザーデータ + */ + public getUserDataValue(i: number): string { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Value) + .getRawString(); + } + + private _json: CubismJson; +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodeluserdatajson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModelUserDataJson = $.CubismModelUserDataJson; + export type CubismModelUserDataJson = $.CubismModelUserDataJson; +} diff --git a/avatar-h5-renderer/framework/src/model/cubismusermodel.ts b/avatar-h5-renderer/framework/src/model/cubismusermodel.ts new file mode 100644 index 0000000..b760550 --- /dev/null +++ b/avatar-h5-renderer/framework/src/model/cubismusermodel.ts @@ -0,0 +1,521 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismBreath } from '../effect/cubismbreath'; +import { CubismEyeBlink } from '../effect/cubismeyeblink'; +import { CubismPose } from '../effect/cubismpose'; +import { ICubismModelSetting } from '../icubismmodelsetting'; +import { CubismIdHandle } from '../id/cubismid'; +import { Constant } from '../live2dcubismframework'; +import { CubismModelMatrix } from '../math/cubismmodelmatrix'; +import { CubismTargetPoint } from '../math/cubismtargetpoint'; +import { + ACubismMotion, + BeganMotionCallback, + FinishedMotionCallback +} from '../motion/acubismmotion'; +import { CubismExpressionMotion } from '../motion/cubismexpressionmotion'; +import { CubismExpressionMotionManager } from '../motion/cubismexpressionmotionmanager'; +import { CubismMotion } from '../motion/cubismmotion'; +import { CubismMotionManager } from '../motion/cubismmotionmanager'; +import { CubismMotionQueueManager } from '../motion/cubismmotionqueuemanager'; +import { CubismPhysics } from '../physics/cubismphysics'; +import { CubismRenderer_WebGL } from '../rendering/cubismrenderer_webgl'; +import { CubismLogError, CubismLogInfo } from '../utils/cubismdebug'; +import { CubismMoc } from './cubismmoc'; +import { CubismModel } from './cubismmodel'; +import { CubismModelUserData } from './cubismmodeluserdata'; + +/** + * ユーザーが実際に使用するモデル + * + * ユーザーが実際に使用するモデルの基底クラス。これを継承してユーザーが実装する。 + */ +export class CubismUserModel { + /** + * 初期化状態の取得 + * + * 初期化されている状態か? + * + * @return true 初期化されている + * @return false 初期化されていない + */ + public isInitialized(): boolean { + return this._initialized; + } + + /** + * 初期化状態の設定 + * + * 初期化状態を設定する。 + * + * @param v 初期化状態 + */ + public setInitialized(v: boolean): void { + this._initialized = v; + } + + /** + * 更新状態の取得 + * + * 更新されている状態か? + * + * @return true 更新されている + * @return false 更新されていない + */ + public isUpdating(): boolean { + return this._updating; + } + + /** + * 更新状態の設定 + * + * 更新状態を設定する + * + * @param v 更新状態 + */ + public setUpdating(v: boolean): void { + this._updating = v; + } + + /** + * マウスドラッグ情報の設定 + * + * @param ドラッグしているカーソルのX位置 + * @param ドラッグしているカーソルのY位置 + */ + public setDragging(x: number, y: number): void { + this._dragManager.set(x, y); + } + + /** + * モデル行列を取得する + * @return モデル行列 + */ + public getModelMatrix(): CubismModelMatrix { + return this._modelMatrix; + } + + /** + * モデルを描画したバッファを設定する + * + * @param width モデルを描画したバッファの幅 + * @param height モデルを描画したバッファの高さ + */ + public setRenderTargetSize(width: number, height: number): void { + if (this._renderer) { + this._renderer.setRenderTargetSize(width, height); + } + } + + /** + * 不透明度の設定 + * + * @param a 不透明度 + */ + public setOpacity(a: number): void { + this._opacity = a; + } + + /** + * 不透明度の取得 + * + * @return 不透明度 + */ + public getOpacity(): number { + return this._opacity; + } + + /** + * モデルデータを読み込む + * + * @param buffer moc3ファイルが読み込まれているバッファ + */ + public loadModel(buffer: ArrayBuffer, shouldCheckMocConsistency = false) { + this._moc = CubismMoc.create(buffer, shouldCheckMocConsistency); + + if (this._moc == null) { + CubismLogError('Failed to CubismMoc.create().'); + return; + } + + this._model = this._moc.createModel(); + + if (this._model == null) { + CubismLogError('Failed to CreateModel().'); + return; + } + + this._model.saveParameters(); + this._modelMatrix = new CubismModelMatrix( + this._model.getCanvasWidth(), + this._model.getCanvasHeight() + ); + } + + /** + * モーションデータを読み込む + * @param buffer motion3.jsonファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @param name モーションの名前 + * @param onFinishedMotionHandler モーション再生終了時に呼び出されるコールバック関数 + * @param onBeganMotionHandler モーション再生開始時に呼び出されるコールバック関数 + * @param modelSetting モデル設定 + * @param group モーショングループ名 + * @param index モーションインデックス + * @param shouldCheckMotionConsistency motion3.json整合性チェックするかどうか + * @return モーションクラス + */ + public loadMotion( + buffer: ArrayBuffer, + size: number, + name: string, + onFinishedMotionHandler?: FinishedMotionCallback, + onBeganMotionHandler?: BeganMotionCallback, + modelSetting?: ICubismModelSetting, + group?: string, + index?: number, + shouldCheckMotionConsistency: boolean = false + ): CubismMotion { + if (buffer == null || size == 0) { + CubismLogError('Failed to loadMotion().'); + return null; + } + + const motion: CubismMotion = CubismMotion.create( + buffer, + size, + onFinishedMotionHandler, + onBeganMotionHandler, + shouldCheckMotionConsistency + ); + + if (motion == null) { + CubismLogError(`Failed to create motion from buffer in LoadMotion()`); + return null; + } + + // 必要であればモーションフェード値を上書き + if (modelSetting) { + const fadeInTime: number = modelSetting.getMotionFadeInTimeValue( + group, + index + ); + if (fadeInTime >= 0.0) { + motion.setFadeInTime(fadeInTime); + } + + const fadeOutTime = modelSetting.getMotionFadeOutTimeValue(group, index); + if (fadeOutTime >= 0.0) { + motion.setFadeOutTime(fadeOutTime); + } + } + + return motion; + } + + /** + * 表情データの読み込み + * @param buffer expファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @param name 表情の名前 + */ + public loadExpression( + buffer: ArrayBuffer, + size: number, + name: string + ): ACubismMotion { + if (buffer == null || size == 0) { + CubismLogError('Failed to loadExpression().'); + return null; + } + return CubismExpressionMotion.create(buffer, size); + } + + /** + * ポーズデータの読み込み + * @param buffer pose3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadPose(buffer: ArrayBuffer, size: number): void { + if (buffer == null || size == 0) { + CubismLogError('Failed to loadPose().'); + return; + } + this._pose = CubismPose.create(buffer, size); + } + + /** + * モデルに付属するユーザーデータを読み込む + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadUserData(buffer: ArrayBuffer, size: number): void { + if (buffer == null || size == 0) { + CubismLogError('Failed to loadUserData().'); + return; + } + this._modelUserData = CubismModelUserData.create(buffer, size); + } + + /** + * 物理演算データの読み込み + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadPhysics(buffer: ArrayBuffer, size: number): void { + if (buffer == null || size == 0) { + CubismLogError('Failed to loadPhysics().'); + return; + } + this._physics = CubismPhysics.create(buffer, size); + } + + /** + * 当たり判定の取得 + * @param drawableId 検証したいDrawableのID + * @param pointX X位置 + * @param pointY Y位置 + * @return true ヒットしている + * @return false ヒットしていない + */ + public isHit( + drawableId: CubismIdHandle, + pointX: number, + pointY: number + ): boolean { + const drawIndex: number = this._model.getDrawableIndex(drawableId); + + if (drawIndex < 0) { + return false; // 存在しない場合はfalse + } + + const count: number = this._model.getDrawableVertexCount(drawIndex); + const vertices: Float32Array = this._model.getDrawableVertices(drawIndex); + + let left: number = vertices[0]; + let right: number = vertices[0]; + let top: number = vertices[1]; + let bottom: number = vertices[1]; + + for (let j = 1; j < count; ++j) { + const x = vertices[Constant.vertexOffset + j * Constant.vertexStep]; + const y = vertices[Constant.vertexOffset + j * Constant.vertexStep + 1]; + + if (x < left) { + left = x; // Min x + } + + if (x > right) { + right = x; // Max x + } + + if (y < top) { + top = y; // Min y + } + + if (y > bottom) { + bottom = y; // Max y + } + } + + const tx: number = this._modelMatrix.invertTransformX(pointX); + const ty: number = this._modelMatrix.invertTransformY(pointY); + + return left <= tx && tx <= right && top <= ty && ty <= bottom; + } + + /** + * モデルの取得 + * @return モデル + */ + public getModel(): CubismModel { + return this._model; + } + + /** + * 読み込めないMocファイルの.moc3 Versionを取得 + * @param mocBytes 読み込めないMocファイルのバイト配列 + * @returns .moc3 Version番号 + */ + public getMocVersionFromBuffer(mocBytes: ArrayBuffer): number { + return CubismMoc.getMocVersionFromBuffer(mocBytes); + } + + /** + * レンダラの取得 + * @return レンダラ + */ + public getRenderer(): CubismRenderer_WebGL { + return this._renderer; + } + + /** + * レンダラを作成して初期化を実行する + * @param width レンダリングする幅 + * @param height レンダリングする高さ + * @param maskBufferCount バッファの生成数 + */ + public createRenderer( + width: number, + height: number, + maskBufferCount = 1 + ): void { + if (this._renderer) { + this.deleteRenderer(); + } + + this._renderer = new CubismRenderer_WebGL(width, height); + this._renderer.initialize(this._model, maskBufferCount); + } + + /** + * レンダラの解放 + */ + public deleteRenderer(): void { + if (this._renderer != null) { + this._renderer.release(); + this._renderer = null; + } + } + + /** + * イベント発火時の標準処理 + * + * Eventが再生処理時にあった場合の処理をする。 + * 継承で上書きすることを想定している。 + * 上書きしない場合はログ出力をする。 + * + * @param eventValue 発火したイベントの文字列データ + */ + public motionEventFired(eventValue: string): void { + CubismLogInfo('{0}', eventValue); + } + + /** + * イベント用のコールバック + * + * CubismMotionQueueManagerにイベント用に登録するためのCallback。 + * CubismUserModelの継承先のEventFiredを呼ぶ。 + * + * @param caller 発火したイベントを管理していたモーションマネージャー、比較用 + * @param eventValue 発火したイベントの文字列データ + * @param customData CubismUserModelを継承したインスタンスを想定 + */ + public static cubismDefaultMotionEventCallback( + caller: CubismMotionQueueManager, + eventValue: string, + customData: CubismUserModel + ): void { + const model: CubismUserModel = customData; + + if (model != null) { + model.motionEventFired(eventValue); + } + } + + /** + * コンストラクタ + */ + public constructor() { + // 各変数初期化 + this._moc = null; + this._model = null; + this._motionManager = null; + this._expressionManager = null; + this._eyeBlink = null; + this._breath = null; + this._modelMatrix = null; + this._pose = null; + this._dragManager = null; + this._physics = null; + this._modelUserData = null; + this._initialized = false; + this._updating = false; + this._opacity = 1.0; + this._mocConsistency = false; + this._debugMode = false; + this._renderer = null; + + // モーションマネージャーを作成 + this._motionManager = new CubismMotionManager(); + this._motionManager.setEventCallback( + CubismUserModel.cubismDefaultMotionEventCallback, + this + ); + + // 表情マネージャーを作成 + this._expressionManager = new CubismExpressionMotionManager(); + + // ドラッグによるアニメーション + this._dragManager = new CubismTargetPoint(); + } + + /** + * デストラクタに相当する処理 + */ + public release() { + if (this._motionManager != null) { + this._motionManager.release(); + this._motionManager = null; + } + + if (this._expressionManager != null) { + this._expressionManager.release(); + this._expressionManager = null; + } + + if (this._moc != null) { + this._moc.deleteModel(this._model); + this._moc.release(); + this._moc = null; + } + + this._modelMatrix = null; + + CubismPose.delete(this._pose); + CubismEyeBlink.delete(this._eyeBlink); + CubismBreath.delete(this._breath); + + this._dragManager = null; + + CubismPhysics.delete(this._physics); + CubismModelUserData.delete(this._modelUserData); + + this.deleteRenderer(); + } + + protected _moc: CubismMoc; // Mocデータ + protected _model: CubismModel; // Modelインスタンス + + protected _motionManager: CubismMotionManager; // モーション管理 + protected _expressionManager: CubismExpressionMotionManager; // 表情管理 + protected _eyeBlink: CubismEyeBlink; // 自動まばたき + protected _breath: CubismBreath; // 呼吸 + protected _modelMatrix: CubismModelMatrix; // モデル行列 + protected _pose: CubismPose; // ポーズ管理 + protected _dragManager: CubismTargetPoint; // マウスドラッグ + protected _physics: CubismPhysics; // 物理演算 + protected _modelUserData: CubismModelUserData; // ユーザーデータ + + protected _initialized: boolean; // 初期化されたかどうか + protected _updating: boolean; // 更新されたかどうか + protected _opacity: number; // 不透明度 + protected _mocConsistency: boolean; // MOC3整合性検証するかどうか + protected _motionConsistency: boolean; // motion3.json整合性検証するかどうか + protected _debugMode: boolean; // デバッグモードかどうか + + private _renderer: CubismRenderer_WebGL; // レンダラ +} + +// Namespace definition for compatibility. +import * as $ from './cubismusermodel'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismUserModel = $.CubismUserModel; + export type CubismUserModel = $.CubismUserModel; +} diff --git a/avatar-h5-renderer/framework/src/motion/acubismmotion.ts b/avatar-h5-renderer/framework/src/motion/acubismmotion.ts new file mode 100644 index 0000000..c43dba4 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/acubismmotion.ts @@ -0,0 +1,443 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from '../math/cubismmath'; +import { CubismModel } from '../model/cubismmodel'; +import { CSM_ASSERT, CubismDebug } from '../utils/cubismdebug'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; + +/** モーション再生開始コールバック関数定義 */ +export type BeganMotionCallback = (self: ACubismMotion) => void; + +/** モーション再生終了コールバック関数定義 */ +export type FinishedMotionCallback = (self: ACubismMotion) => void; + +/** + * モーションの抽象基底クラス + * + * モーションの抽象基底クラス。MotionQueueManagerによってモーションの再生を管理する。 + */ +export abstract class ACubismMotion { + /** + * インスタンスの破棄 + */ + public static delete(motion: ACubismMotion): void { + motion.release(); + motion = null; + } + + /** + * コンストラクタ + */ + public constructor() { + this._fadeInSeconds = -1.0; + this._fadeOutSeconds = -1.0; + this._weight = 1.0; + this._offsetSeconds = 0.0; // 再生の開始時刻 + this._isLoop = false; // ループするか + this._isLoopFadeIn = true; // ループ時にフェードインが有効かどうかのフラグ。初期値では有効。 + this._previousLoopState = this._isLoop; + this._firedEventValues = new Array(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._weight = 0.0; + } + + /** + * モデルのパラメータ + * @param model 対象のモデル + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @param userTimeSeconds デルタ時間の積算値[秒] + */ + public updateParameters( + model: CubismModel, + motionQueueEntry: CubismMotionQueueEntry, + userTimeSeconds: number + ): void { + if (!motionQueueEntry.isAvailable() || motionQueueEntry.isFinished()) { + return; + } + + this.setupMotionQueueEntry(motionQueueEntry, userTimeSeconds); + + const fadeWeight = this.updateFadeWeight(motionQueueEntry, userTimeSeconds); + + //---- 全てのパラメータIDをループする ---- + this.doUpdateParameters( + model, + userTimeSeconds, + fadeWeight, + motionQueueEntry + ); + + // 後処理 + // 終了時刻を過ぎたら終了フラグを立てる(CubismMotionQueueManager) + if ( + motionQueueEntry.getEndTime() > 0 && + motionQueueEntry.getEndTime() < userTimeSeconds + ) { + motionQueueEntry.setIsFinished(true); // 終了 + } + } + + /** + * @brief モデルの再生開始処理 + * + * モーションの再生を開始するためのセットアップを行う。 + * + * @param[in] motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @param[in] userTimeSeconds デルタ時間の積算値[秒] + */ + public setupMotionQueueEntry( + motionQueueEntry: CubismMotionQueueEntry, + userTimeSeconds: number + ) { + if (motionQueueEntry == null || motionQueueEntry.isStarted()) { + return; + } + + if (!motionQueueEntry.isAvailable()) { + return; + } + + motionQueueEntry.setIsStarted(true); + motionQueueEntry.setStartTime(userTimeSeconds - this._offsetSeconds); // モーションの開始時刻を記録 + motionQueueEntry.setFadeInStartTime(userTimeSeconds); // フェードインの開始時刻 + + if (motionQueueEntry.getEndTime() < 0.0) { + // 開始していないうちに終了設定している場合がある + this.adjustEndTime(motionQueueEntry); + } + + // 再生開始コールバック + if (motionQueueEntry._motion._onBeganMotion) { + motionQueueEntry._motion._onBeganMotion(motionQueueEntry._motion); + } + } + + /** + * @brief モデルのウェイト更新 + * + * モーションのウェイトを更新する。 + * + * @param[in] motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @param[in] userTimeSeconds デルタ時間の積算値[秒] + */ + public updateFadeWeight( + motionQueueEntry: CubismMotionQueueEntry, + userTimeSeconds: number + ): number { + if (motionQueueEntry == null) { + CubismDebug.print(LogLevel.LogLevel_Error, 'motionQueueEntry is null.'); + } + + let fadeWeight: number = this._weight; // 現在の値と掛け合わせる割合 + + //---- フェードイン・アウトの処理 ---- + // 単純なサイン関数でイージングする + const fadeIn: number = + this._fadeInSeconds == 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + this._fadeInSeconds + ); + + const fadeOut: number = + this._fadeOutSeconds == 0.0 || motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + this._fadeOutSeconds + ); + + fadeWeight = fadeWeight * fadeIn * fadeOut; + + motionQueueEntry.setState(userTimeSeconds, fadeWeight); + + CSM_ASSERT(0.0 <= fadeWeight && fadeWeight <= 1.0); + + return fadeWeight; + } + + /** + * フェードインの時間を設定する + * @param fadeInSeconds フェードインにかかる時間[秒] + */ + public setFadeInTime(fadeInSeconds: number): void { + this._fadeInSeconds = fadeInSeconds; + } + + /** + * フェードアウトの時間を設定する + * @param fadeOutSeconds フェードアウトにかかる時間[秒] + */ + public setFadeOutTime(fadeOutSeconds: number): void { + this._fadeOutSeconds = fadeOutSeconds; + } + + /** + * フェードアウトにかかる時間の取得 + * @return フェードアウトにかかる時間[秒] + */ + public getFadeOutTime(): number { + return this._fadeOutSeconds; + } + + /** + * フェードインにかかる時間の取得 + * @return フェードインにかかる時間[秒] + */ + public getFadeInTime(): number { + return this._fadeInSeconds; + } + + /** + * モーション適用の重みの設定 + * @param weight 重み(0.0 - 1.0) + */ + public setWeight(weight: number): void { + this._weight = weight; + } + + /** + * モーション適用の重みの取得 + * @return 重み(0.0 - 1.0) + */ + public getWeight(): number { + return this._weight; + } + + /** + * モーションの長さの取得 + * @return モーションの長さ[秒] + * + * @note ループの時は「-1」。 + * ループでない場合は、オーバーライドする。 + * 正の値の時は取得される時間で終了する。 + * 「-1」の時は外部から停止命令がない限り終わらない処理となる。 + */ + public getDuration(): number { + return -1.0; + } + + /** + * モーションのループ1回分の長さの取得 + * @return モーションのループ一回分の長さ[秒] + * + * @note ループしない場合は、getDuration()と同じ値を返す + * ループ一回分の長さが定義できない場合(プログラム的に動き続けるサブクラスなど)の場合は「-1」を返す + */ + public getLoopDuration(): number { + return -1.0; + } + + /** + * モーション再生の開始時刻の設定 + * @param offsetSeconds モーション再生の開始時刻[秒] + */ + public setOffsetTime(offsetSeconds: number): void { + this._offsetSeconds = offsetSeconds; + } + + /** + * ループ情報の設定 + * @param loop ループ情報 + */ + public setLoop(loop: boolean): void { + this._isLoop = loop; + } + + /** + * ループ情報の取得 + * @return true ループする + * @return false ループしない + */ + public getLoop(): boolean { + return this._isLoop; + } + + /** + * ループ時のフェードイン情報の設定 + * @param loopFadeIn ループ時のフェードイン情報 + */ + public setLoopFadeIn(loopFadeIn: boolean) { + this._isLoopFadeIn = loopFadeIn; + } + + /** + * ループ時のフェードイン情報の取得 + * + * @return true する + * @return false しない + */ + public getLoopFadeIn(): boolean { + return this._isLoopFadeIn; + } + + /** + * モデルのパラメータ更新 + * + * イベント発火のチェック。 + * 入力する時間は呼ばれるモーションタイミングを0とした秒数で行う。 + * + * @param beforeCheckTimeSeconds 前回のイベントチェック時間[秒] + * @param motionTimeSeconds 今回の再生時間[秒] + */ + public getFiredEvent( + beforeCheckTimeSeconds: number, + motionTimeSeconds: number + ): Array { + return this._firedEventValues; + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @param weight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @return true モデルへパラメータ値の反映あり + * @return false モデルへのパラメータ値の反映なし(モーションの変化なし) + */ + public abstract doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + weight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void; + + /** + * モーション再生開始コールバックの登録 + * + * モーション再生開始コールバックを登録する。 + * 以下の状態の際には呼び出されない: + * 1. 再生中のモーションが「ループ」として設定されているとき + * 2. コールバックが登録されていない時 + * + * @param onBeganMotionHandler モーション再生開始コールバック関数 + */ + public setBeganMotionHandler = (onBeganMotionHandler: BeganMotionCallback) => + (this._onBeganMotion = onBeganMotionHandler); + + /** + * モーション再生開始コールバックの取得 + * + * モーション再生開始コールバックを取得する。 + * + * @return 登録されているモーション再生開始コールバック関数 + */ + public getBeganMotionHandler = () => this._onBeganMotion; + + /** + * モーション再生終了コールバックの登録 + * + * モーション再生終了コールバックを登録する。 + * isFinishedフラグを設定するタイミングで呼び出される。 + * 以下の状態の際には呼び出されない: + * 1. 再生中のモーションが「ループ」として設定されているとき + * 2. コールバックが登録されていない時 + * + * @param onFinishedMotionHandler モーション再生終了コールバック関数 + */ + public setFinishedMotionHandler = ( + onFinishedMotionHandler: FinishedMotionCallback + ) => (this._onFinishedMotion = onFinishedMotionHandler); + + /** + * モーション再生終了コールバックの取得 + * + * モーション再生終了コールバックを取得する。 + * + * @return 登録されているモーション再生終了コールバック関数 + */ + public getFinishedMotionHandler = () => this._onFinishedMotion; + + /** + * 透明度のカーブが存在するかどうかを確認する + * + * @return true -> キーが存在する + * false -> キーが存在しない + */ + public isExistModelOpacity(): boolean { + return false; + } + + /** + * 透明度のカーブのインデックスを返す + * + * @return success:透明度のカーブのインデックス + */ + public getModelOpacityIndex(): number { + return -1; + } + + /** + * 透明度のIdを返す + * + * @param index モーションカーブのインデックス + * @return success:透明度のId + */ + public getModelOpacityId(index: number): CubismIdHandle { + return null; + } + + /** + * 指定時間の透明度の値を返す + * + * @return success:モーションの現在時間におけるOpacityの値 + * + * @note 更新後の値を取るにはUpdateParameters() の後に呼び出す。 + */ + protected getModelOpacityValue(): number { + return 1.0; + } + + /** + * 終了時刻の調整 + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + */ + protected adjustEndTime(motionQueueEntry: CubismMotionQueueEntry) { + const duration = this.getDuration(); + + // duration == -1 の場合はループする + const endTime = + duration <= 0.0 ? -1 : motionQueueEntry.getStartTime() + duration; + + motionQueueEntry.setEndTime(endTime); + } + + public _fadeInSeconds: number; // フェードインにかかる時間[秒] + public _fadeOutSeconds: number; // フェードアウトにかかる時間[秒] + public _weight: number; // モーションの重み + public _offsetSeconds: number; // モーション再生の開始時間[秒] + public _isLoop: boolean; // ループが有効かのフラグ + public _isLoopFadeIn: boolean; // ループ時にフェードインが有効かどうかのフラグ + public _previousLoopState: boolean; // 前回の `_isLoop` の状態 + public _firedEventValues: Array; + + // モーション再生開始コールバック関数 + public _onBeganMotion?: BeganMotionCallback; + // モーション再生終了コールバック関数 + public _onFinishedMotion?: FinishedMotionCallback; +} + +// Namespace definition for compatibility. +import * as $ from './acubismmotion'; +import { CubismIdHandle } from '../id/cubismid'; +import { LogLevel } from '../live2dcubismframework'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const ACubismMotion = $.ACubismMotion; + export type ACubismMotion = $.ACubismMotion; + export type BeganMotionCallback = $.BeganMotionCallback; + export type FinishedMotionCallback = $.FinishedMotionCallback; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismbreathupdater.ts b/avatar-h5-renderer/framework/src/motion/cubismbreathupdater.ts new file mode 100644 index 0000000..4961dd6 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismbreathupdater.ts @@ -0,0 +1,60 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismUpdater, CubismUpdateOrder } from './icubismupdater'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismBreath } from '../effect/cubismbreath'; + +/** + * Updater for breath effects. + * Handles the management of breath animation through the CubismBreath class. + */ +export class CubismBreathUpdater extends ICubismUpdater { + private _breath: CubismBreath; + + /** + * Constructor + * + * @param breath CubismBreath reference + */ + constructor(breath: CubismBreath); + + /** + * Constructor + * + * @param breath CubismBreath reference + * @param executionOrder Order of operations + */ + constructor(breath: CubismBreath, executionOrder: number); + + constructor(breath: CubismBreath, executionOrder?: number) { + super(executionOrder ?? CubismUpdateOrder.CubismUpdateOrder_Breath); + this._breath = breath; + } + + /** + * Update process. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void { + if (!model) { + return; + } + + this._breath.updateParameters(model, deltaTimeSeconds); + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismbreathupdater'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismBreathUpdater = $.CubismBreathUpdater; + export type CubismBreathUpdater = $.CubismBreathUpdater; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismexpressionmotion.ts b/avatar-h5-renderer/framework/src/motion/cubismexpressionmotion.ts new file mode 100644 index 0000000..34cb3ff --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismexpressionmotion.ts @@ -0,0 +1,365 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismJson, Value } from '../utils/cubismjson'; +import { ACubismMotion } from './acubismmotion'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; + +// exp3.jsonのキーとデフォルト +const ExpressionKeyFadeIn = 'FadeInTime'; +const ExpressionKeyFadeOut = 'FadeOutTime'; +const ExpressionKeyParameters = 'Parameters'; +const ExpressionKeyId = 'Id'; +const ExpressionKeyValue = 'Value'; +const ExpressionKeyBlend = 'Blend'; +const BlendValueAdd = 'Add'; +const BlendValueMultiply = 'Multiply'; +const BlendValueOverwrite = 'Overwrite'; +const DefaultFadeTime = 1.0; + +/** + * 表情のモーション + * + * 表情のモーションクラス。 + */ +export class CubismExpressionMotion extends ACubismMotion { + static readonly DefaultAdditiveValue = 0.0; // 加算適用の初期値 + static readonly DefaultMultiplyValue = 1.0; // 乗算適用の初期値 + + /** + * インスタンスを作成する。 + * @param buffer expファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create( + buffer: ArrayBuffer, + size: number + ): CubismExpressionMotion { + const expression: CubismExpressionMotion = new CubismExpressionMotion(); + expression.parse(buffer, size); + return expression; + } + + /** + * モデルのパラメータの更新の実行 + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @param weight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + */ + public doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + weight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void { + for (let i = 0; i < this._parameters.length; ++i) { + const parameter: ExpressionParameter = this._parameters[i]; + + switch (parameter.blendType) { + case ExpressionBlendType.Additive: { + model.addParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + case ExpressionBlendType.Multiply: { + model.multiplyParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + case ExpressionBlendType.Overwrite: { + model.setParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + default: + // 仕様にない値を設定した時はすでに加算モードになっている + break; + } + } + } + + /** + * @brief 表情によるモデルのパラメータの計算 + * + * モデルの表情に関するパラメータを計算する。 + * + * @param[in] model 対象のモデル + * @param[in] userTimeSeconds デルタ時間の積算値[秒] + * @param[in] motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @param[in] expressionParameterValues モデルに適用する各パラメータの値 + * @param[in] expressionIndex 表情のインデックス + * @param[in] fadeWeight 表情のウェイト + */ + public calculateExpressionParameters( + model: CubismModel, + userTimeSeconds: number, + motionQueueEntry: CubismMotionQueueEntry, + expressionParameterValues: Array, + expressionIndex: number, + fadeWeight: number + ) { + if (motionQueueEntry == null || expressionParameterValues == null) { + return; + } + + if (!motionQueueEntry.isAvailable()) { + return; + } + + // モデルに適用する値を計算 + for (let i = 0; i < expressionParameterValues.length; ++i) { + const expressionParameterValue = expressionParameterValues[i]; + + if (expressionParameterValue.parameterId == null) { + continue; + } + + const currentParameterValue = (expressionParameterValue.overwriteValue = + model.getParameterValueById(expressionParameterValue.parameterId)); + + const expressionParameters = this.getExpressionParameters(); + let parameterIndex = -1; + for (let j = 0; j < expressionParameters.length; ++j) { + if ( + expressionParameterValue.parameterId != + expressionParameters[j].parameterId + ) { + continue; + } + + parameterIndex = j; + + break; + } + + // 再生中のExpressionが参照していないパラメータは初期値を適用 + if (parameterIndex < 0) { + if (expressionIndex == 0) { + expressionParameterValue.additiveValue = + CubismExpressionMotion.DefaultAdditiveValue; + expressionParameterValue.multiplyValue = + CubismExpressionMotion.DefaultMultiplyValue; + expressionParameterValue.overwriteValue = currentParameterValue; + } else { + expressionParameterValue.additiveValue = this.calculateValue( + expressionParameterValue.additiveValue, + CubismExpressionMotion.DefaultAdditiveValue, + fadeWeight + ); + expressionParameterValue.multiplyValue = this.calculateValue( + expressionParameterValue.multiplyValue, + CubismExpressionMotion.DefaultMultiplyValue, + fadeWeight + ); + expressionParameterValue.overwriteValue = this.calculateValue( + expressionParameterValue.overwriteValue, + currentParameterValue, + fadeWeight + ); + } + continue; + } + + // 値を計算 + const value = expressionParameters[parameterIndex].value; + let newAdditiveValue, newMultiplyValue, newOverwriteValue; + switch (expressionParameters[parameterIndex].blendType) { + case ExpressionBlendType.Additive: + newAdditiveValue = value; + newMultiplyValue = CubismExpressionMotion.DefaultMultiplyValue; + newOverwriteValue = currentParameterValue; + break; + + case ExpressionBlendType.Multiply: + newAdditiveValue = CubismExpressionMotion.DefaultAdditiveValue; + newMultiplyValue = value; + newOverwriteValue = currentParameterValue; + break; + + case ExpressionBlendType.Overwrite: + newAdditiveValue = CubismExpressionMotion.DefaultAdditiveValue; + newMultiplyValue = CubismExpressionMotion.DefaultMultiplyValue; + newOverwriteValue = value; + break; + + default: + return; + } + + if (expressionIndex == 0) { + expressionParameterValue.additiveValue = newAdditiveValue; + expressionParameterValue.multiplyValue = newMultiplyValue; + expressionParameterValue.overwriteValue = newOverwriteValue; + } else { + expressionParameterValue.additiveValue = + expressionParameterValue.additiveValue * (1.0 - fadeWeight) + + newAdditiveValue * fadeWeight; + expressionParameterValue.multiplyValue = + expressionParameterValue.multiplyValue * (1.0 - fadeWeight) + + newMultiplyValue * fadeWeight; + expressionParameterValue.overwriteValue = + expressionParameterValue.overwriteValue * (1.0 - fadeWeight) + + newOverwriteValue * fadeWeight; + } + } + } + + /** + * @brief 表情が参照しているパラメータを取得 + * + * 表情が参照しているパラメータを取得する + * + * @return 表情パラメータ + */ + public getExpressionParameters() { + return this._parameters; + } + + protected parse(buffer: ArrayBuffer, size: number) { + const json: CubismJson = CubismJson.create(buffer, size); + if (!json) { + return; + } + + const root: Value = json.getRoot(); + + this.setFadeInTime( + root.getValueByString(ExpressionKeyFadeIn).toFloat(DefaultFadeTime) + ); // フェードイン + this.setFadeOutTime( + root.getValueByString(ExpressionKeyFadeOut).toFloat(DefaultFadeTime) + ); // フェードアウト + + // 各パラメータについて + const parameterCount = root + .getValueByString(ExpressionKeyParameters) + .getSize(); + + let dstIndex: number = this._parameters.length; + this._parameters.length += parameterCount; + for (let i = 0; i < parameterCount; ++i) { + const param: Value = root + .getValueByString(ExpressionKeyParameters) + .getValueByIndex(i); + const parameterId: CubismIdHandle = CubismFramework.getIdManager().getId( + param.getValueByString(ExpressionKeyId).getRawString() + ); // パラメータID + + const value: number = param + .getValueByString(ExpressionKeyValue) + .toFloat(); // 値 + + // 計算方法の設定 + let blendType: ExpressionBlendType; + + if ( + param.getValueByString(ExpressionKeyBlend).isNull() || + param.getValueByString(ExpressionKeyBlend).getString() == BlendValueAdd + ) { + blendType = ExpressionBlendType.Additive; + } else if ( + param.getValueByString(ExpressionKeyBlend).getString() == + BlendValueMultiply + ) { + blendType = ExpressionBlendType.Multiply; + } else if ( + param.getValueByString(ExpressionKeyBlend).getString() == + BlendValueOverwrite + ) { + blendType = ExpressionBlendType.Overwrite; + } else { + // その他 仕様にない値を設定した時は加算モードにすることで復旧 + blendType = ExpressionBlendType.Additive; + } + + // 設定オブジェクトを作成してリストに追加する + const item: ExpressionParameter = new ExpressionParameter(); + + item.parameterId = parameterId; + item.blendType = blendType; + item.value = value; + + this._parameters[dstIndex++] = item; + } + + CubismJson.delete(json); // JSONデータは不要になったら削除する + } + + /** + * @brief ブレンド計算 + * + * 入力された値でブレンド計算をする。 + * + * @param source 現在の値 + * @param destination 適用する値 + * @param weight ウェイト + * @return 計算結果 + */ + public calculateValue( + source: number, + destination: number, + fadeWeight: number + ): number { + return source * (1.0 - fadeWeight) + destination * fadeWeight; + } + + /** + * コンストラクタ + */ + protected constructor() { + super(); + this._parameters = new Array(); + } + + private _parameters: Array; // 表情のパラメータ情報リスト +} + +/** + * 表情パラメータ値の計算方式 + */ +export enum ExpressionBlendType { + Additive = 0, // 加算 + Multiply = 1, // 乗算 + Overwrite = 2 // 上書き +} + +/** + * 表情のパラメータ情報 + */ +export class ExpressionParameter { + parameterId: CubismIdHandle; // パラメータID + blendType: ExpressionBlendType; // パラメータの演算種類 + value: number; // 値 +} + +// Namespace definition for compatibility. +import * as $ from './cubismexpressionmotion'; +import { ExpressionParameterValue } from './cubismexpressionmotionmanager'; +import { CubismDefaultParameterId } from '../cubismdefaultparameterid'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismExpressionMotion = $.CubismExpressionMotion; + export type CubismExpressionMotion = $.CubismExpressionMotion; + export const ExpressionBlendType = $.ExpressionBlendType; + export type ExpressionBlendType = $.ExpressionBlendType; + export const ExpressionParameter = $.ExpressionParameter; + export type ExpressionParameter = $.ExpressionParameter; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismexpressionmotionmanager.ts b/avatar-h5-renderer/framework/src/motion/cubismexpressionmotionmanager.ts new file mode 100644 index 0000000..e50a3b9 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismexpressionmotionmanager.ts @@ -0,0 +1,282 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismId, CubismIdHandle } from '../id/cubismid'; +import { LogLevel, csmDelete } from '../live2dcubismframework'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismExpressionMotion } from './cubismexpressionmotion'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; +import { CubismMotionQueueManager } from './cubismmotionqueuemanager'; + +/** + * @brief パラメータに適用する表情の値を持たせる構造体 + */ +export class ExpressionParameterValue { + parameterId: CubismIdHandle; // パラメーターID + additiveValue: number; // 加算値 + multiplyValue: number; // 乗算値 + overwriteValue: number; // 上書き値 +} + +/** + * @brief 表情モーションの管理 + * + * 表情モーションの管理をおこなうクラス。 + */ +export class CubismExpressionMotionManager extends CubismMotionQueueManager { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._expressionParameterValues = new Array(); + this._fadeWeights = new Array(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._expressionParameterValues) { + csmDelete(this._expressionParameterValues); + this._expressionParameterValues = null; + } + + if (this._fadeWeights) { + csmDelete(this._fadeWeights); + this._fadeWeights = null; + } + } + + /** + * @brief 再生中のモーションのウェイトを取得する。 + * + * @param[in] index 表情のインデックス + * @return 表情モーションのウェイト + */ + public getFadeWeight(index: number): number { + if ( + index < 0 || + this._fadeWeights.length < 1 || + index >= this._fadeWeights.length + ) { + console.warn( + 'Failed to get the fade weight value. The element at that index does not exist.' + ); + return -1; + } + + return this._fadeWeights[index]; + } + + /** + * @brief モーションのウェイトの設定。 + * + * @param[in] index 表情のインデックス + * @param[in] index 表情モーションのウェイト + */ + public setFadeWeight(index: number, expressionFadeWeight: number): void { + if ( + index < 0 || + this._fadeWeights.length < 1 || + this._fadeWeights.length <= index + ) { + console.warn( + 'Failed to set the fade weight value. The element at that index does not exist.' + ); + return; + } + + this._fadeWeights[index] = expressionFadeWeight; + } + + /** + * @brief モーションの更新 + * + * モーションを更新して、モデルにパラメータ値を反映する。 + * + * @param[in] model 対象のモデル + * @param[in] deltaTimeSeconds デルタ時間[秒] + * @return true 更新されている + * false 更新されていない + */ + public updateMotion(model: CubismModel, deltaTimeSeconds: number): boolean { + this._userTimeSeconds += deltaTimeSeconds; + let updated = false; + const motions = this.getCubismMotionQueueEntries(); + + let expressionWeight = 0.0; + let expressionIndex = 0; + + if (this._fadeWeights.length !== motions.length) { + const difference = motions.length - this._fadeWeights.length; + let dstIndex: number = this._fadeWeights.length; + this._fadeWeights.length += difference; + + // TODO: + // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/fill + // this._fadeWeights.fill(0.0, dstIndex, this._fadeWeights.length) + + for (let i = 0; i < difference; i++) { + this._fadeWeights[dstIndex++] = 0.0; + } + } + + // ------- 処理を行う -------- + // 既にモーションがあれば終了フラグを立てる + for (let i = 0; i < this._motions.length; ) { + const motionQueueEntry = this._motions[i]; + + if (motionQueueEntry == null) { + motions.splice(i, 1); //削除 + continue; + } + + const expressionMotion = ( + motionQueueEntry.getCubismMotion() + ); + + if (expressionMotion == null) { + csmDelete(motionQueueEntry); + motions.splice(i, 1); //削除 + continue; + } + + const expressionParameters = expressionMotion.getExpressionParameters(); + + if (motionQueueEntry.isAvailable()) { + // 再生中のExpressionが参照しているパラメータをすべてリストアップ + for (let i = 0; i < expressionParameters.length; ++i) { + if (expressionParameters[i].parameterId == null) { + continue; + } + + let index = -1; + // リストにパラメータIDが存在するか検索 + for (let j = 0; j < this._expressionParameterValues.length; ++j) { + if ( + this._expressionParameterValues[j].parameterId != + expressionParameters[i].parameterId + ) { + continue; + } + + index = j; + break; + } + + if (index >= 0) { + continue; + } + + // パラメータがリストに存在しないなら新規追加 + const item: ExpressionParameterValue = new ExpressionParameterValue(); + item.parameterId = expressionParameters[i].parameterId; + item.additiveValue = CubismExpressionMotion.DefaultAdditiveValue; + item.multiplyValue = CubismExpressionMotion.DefaultMultiplyValue; + item.overwriteValue = model.getParameterValueById(item.parameterId); + this._expressionParameterValues.push(item); + } + } + + // ------ 値を計算する ------ + expressionMotion.setupMotionQueueEntry( + motionQueueEntry, + this._userTimeSeconds + ); + this.setFadeWeight( + expressionIndex, + expressionMotion.updateFadeWeight( + motionQueueEntry, + this._userTimeSeconds + ) + ); + expressionMotion.calculateExpressionParameters( + model, + this._userTimeSeconds, + motionQueueEntry, + this._expressionParameterValues, + expressionIndex, + this.getFadeWeight(expressionIndex) + ); + + expressionWeight += + expressionMotion.getFadeInTime() == 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (this._userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + expressionMotion.getFadeInTime() + ); + + updated = true; + + if (motionQueueEntry.isTriggeredFadeOut()) { + // フェードアウト開始 + motionQueueEntry.startFadeOut( + motionQueueEntry.getFadeOutSeconds(), + this._userTimeSeconds + ); + } + + ++i; + ++expressionIndex; + } + + // ----- 最新のExpressionのフェードが完了していればそれ以前を削除する ------ + if (motions.length > 1) { + const latestFadeWeight: number = this.getFadeWeight( + this._fadeWeights.length - 1 + ); + if (latestFadeWeight >= 1.0) { + // 配列の最後の要素は削除しない + for (let i = motions.length - 2; i >= 0; --i) { + const motionQueueEntry = motions[i]; + csmDelete(motionQueueEntry); + motions.splice(i, 1); + this._fadeWeights.splice(i, 1); + } + } + } + + if (expressionWeight > 1.0) { + expressionWeight = 1.0; + } + + // モデルに各値を適用 + for (let i = 0; i < this._expressionParameterValues.length; ++i) { + const expressionParameterValue = this._expressionParameterValues[i]; + model.setParameterValueById( + expressionParameterValue.parameterId, + (expressionParameterValue.overwriteValue + + expressionParameterValue.additiveValue) * + expressionParameterValue.multiplyValue, + expressionWeight + ); + + expressionParameterValue.additiveValue = + CubismExpressionMotion.DefaultAdditiveValue; + expressionParameterValue.multiplyValue = + CubismExpressionMotion.DefaultMultiplyValue; + } + + return updated; + } + + private _expressionParameterValues: Array; ///< モデルに適用する各パラメータの値 + private _fadeWeights: Array; ///< 再生中の表情のウェイト + private _startExpressionTime: number; ///< 表情の再生開始時刻 +} + +// Namespace definition for compatibility. +import * as $ from './cubismexpressionmotionmanager'; +import { CubismMath } from '../math/cubismmath'; +import { CubismDebug, CubismLogError } from '../utils/cubismdebug'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismExpressionMotionManager = $.CubismExpressionMotionManager; + export type CubismExpressionMotionManager = $.CubismExpressionMotionManager; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismexpressionupdater.ts b/avatar-h5-renderer/framework/src/motion/cubismexpressionupdater.ts new file mode 100644 index 0000000..756a522 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismexpressionupdater.ts @@ -0,0 +1,66 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismUpdater, CubismUpdateOrder } from './icubismupdater'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismExpressionMotionManager } from './cubismexpressionmotionmanager'; + +/** + * Updater for expression effects. + * Handles the management of expression motion through the CubismExpressionMotionManager. + */ +export class CubismExpressionUpdater extends ICubismUpdater { + private _expressionManager: CubismExpressionMotionManager; + + /** + * Constructor + * + * @param expressionManager CubismExpressionMotionManager reference + */ + constructor(expressionManager: CubismExpressionMotionManager); + + /** + * Constructor + * + * @param expressionManager CubismExpressionMotionManager reference + * @param executionOrder Order of operations + */ + constructor( + expressionManager: CubismExpressionMotionManager, + executionOrder: number + ); + + constructor( + expressionManager: CubismExpressionMotionManager, + executionOrder?: number + ) { + super(executionOrder ?? CubismUpdateOrder.CubismUpdateOrder_Expression); + this._expressionManager = expressionManager; + } + + /** + * Update process. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void { + if (!model) { + return; + } + + this._expressionManager.updateMotion(model, deltaTimeSeconds); + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismexpressionupdater'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismExpressionUpdater = $.CubismExpressionUpdater; + export type CubismExpressionUpdater = $.CubismExpressionUpdater; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismeyeblinkupdater.ts b/avatar-h5-renderer/framework/src/motion/cubismeyeblinkupdater.ts new file mode 100644 index 0000000..d332a14 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismeyeblinkupdater.ts @@ -0,0 +1,76 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismUpdater, CubismUpdateOrder } from './icubismupdater'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismEyeBlink } from '../effect/cubismeyeblink'; + +/** + * Updater for eye blink effects. + * Handles the management of eye blink animation through the CubismEyeBlink class. + */ +export class CubismEyeBlinkUpdater extends ICubismUpdater { + private _motionUpdated: () => boolean; + private _eyeBlink: CubismEyeBlink; + + /** + * Constructor + * + * @param motionUpdated Motion update flag reference + * @param eyeBlink CubismEyeBlink reference + */ + constructor(motionUpdated: () => boolean, eyeBlink: CubismEyeBlink); + + /** + * Constructor + * + * @param motionUpdated Motion update flag reference + * @param eyeBlink CubismEyeBlink reference + * @param executionOrder Order of operations + */ + constructor( + motionUpdated: () => boolean, + eyeBlink: CubismEyeBlink, + executionOrder: number + ); + + constructor( + motionUpdated: () => boolean, + eyeBlink: CubismEyeBlink, + executionOrder?: number + ) { + super(executionOrder ?? CubismUpdateOrder.CubismUpdateOrder_EyeBlink); + this._motionUpdated = motionUpdated; + this._eyeBlink = eyeBlink; + } + + /** + * Update process. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void { + if (!model) { + return; + } + + if (!this._motionUpdated()) { + // メインモーションの更新がないとき + // 目パチ + this._eyeBlink.updateParameters(model, deltaTimeSeconds); + } + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismeyeblinkupdater'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismEyeBlinkUpdater = $.CubismEyeBlinkUpdater; + export type CubismEyeBlinkUpdater = $.CubismEyeBlinkUpdater; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismlipsyncupdater.ts b/avatar-h5-renderer/framework/src/motion/cubismlipsyncupdater.ts new file mode 100644 index 0000000..9cbcdae --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismlipsyncupdater.ts @@ -0,0 +1,104 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismUpdater, CubismUpdateOrder } from './icubismupdater'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismIdHandle } from '../id/cubismid'; +import { IParameterProvider } from './iparameterprovider'; + +/** + * Updater for lip sync effects. + * Handles the management of lip sync animation through parameter providers. + */ +export class CubismLipSyncUpdater extends ICubismUpdater { + private _lipSyncIds: Array; + private _audioProvider: IParameterProvider | null; + + /** + * Constructor + * + * @param lipSyncIds Array of lip sync parameter IDs + * @param audioProvider Audio parameter provider + */ + constructor( + lipSyncIds: Array, + audioProvider: IParameterProvider | null + ); + + /** + * Constructor + * + * @param lipSyncIds Array of lip sync parameter IDs + * @param audioProvider Audio parameter provider + * @param executionOrder Order of operations + */ + constructor( + lipSyncIds: Array, + audioProvider: IParameterProvider | null, + executionOrder: number + ); + + constructor( + lipSyncIds: Array, + audioProvider: IParameterProvider | null, + executionOrder?: number + ) { + super(executionOrder ?? CubismUpdateOrder.CubismUpdateOrder_LipSync); + this._lipSyncIds = [...lipSyncIds]; // Copy array + this._audioProvider = audioProvider; + } + + /** + * Update process. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void { + if (!model) { + return; + } + + if (this._audioProvider) { + const updateSuccessful = this._audioProvider.update(deltaTimeSeconds); + if (updateSuccessful) { + const lipSyncValue = this._audioProvider.getParameter(); + + // Apply lip sync value to all registered parameters + for (let i = 0; i < this._lipSyncIds.length; i++) { + model.addParameterValueById(this._lipSyncIds[i], lipSyncValue); + } + } + } + } + + /** + * Set audio parameter provider. + * + * @param audioProvider Audio parameter provider to set + */ + setAudioProvider(audioProvider: IParameterProvider | null): void { + this._audioProvider = audioProvider; + } + + /** + * Get audio parameter provider. + * + * @return Current audio parameter provider + */ + getAudioProvider(): IParameterProvider | null { + return this._audioProvider; + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismlipsyncupdater'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismLipSyncUpdater = $.CubismLipSyncUpdater; + export type CubismLipSyncUpdater = $.CubismLipSyncUpdater; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismlookupdater.ts b/avatar-h5-renderer/framework/src/motion/cubismlookupdater.ts new file mode 100644 index 0000000..536579c --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismlookupdater.ts @@ -0,0 +1,77 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismUpdater, CubismUpdateOrder } from './icubismupdater'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismTargetPoint } from '../math/cubismtargetpoint'; +import { CubismLook } from '../effect/cubismlook'; + +/** + * Updater for look effects. + * Handles the management of dragging motion through the MotionQueueManager. + */ +export class CubismLookUpdater extends ICubismUpdater { + private _look: CubismLook; + private _dragManager: CubismTargetPoint; + + /** + * Constructor + * + * @param look CubismLook reference + * @param dragManager CubismTargetPoint reference + */ + constructor(look: CubismLook, dragManager: CubismTargetPoint); + + /** + * Constructor + * + * @param look CubismLook reference + * @param dragManager CubismTargetPoint reference + * @param executionOrder Order of operations + */ + constructor( + look: CubismLook, + dragManager: CubismTargetPoint, + executionOrder: number + ); + + constructor( + look: CubismLook, + dragManager: CubismTargetPoint, + executionOrder?: number + ) { + super(executionOrder ?? CubismUpdateOrder.CubismUpdateOrder_Drag); + this._look = look; + this._dragManager = dragManager; + } + + /** + * Update process. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void { + if (!model) { + return; + } + + this._dragManager.update(deltaTimeSeconds); + const dragX = this._dragManager.getX(); + const dragY = this._dragManager.getY(); + + this._look.updateParameters(model, dragX, dragY); + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismlookupdater'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismLookUpdater = $.CubismLookUpdater; + export type CubismLookUpdater = $.CubismLookUpdater; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismmotion.ts b/avatar-h5-renderer/framework/src/motion/cubismmotion.ts new file mode 100644 index 0000000..cb495ad --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismmotion.ts @@ -0,0 +1,1235 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { csmDelete, CubismFramework } from '../live2dcubismframework'; +import { CubismMath } from '../math/cubismmath'; +import { updateSize } from '../utils/cubismarrayutils'; +import { CubismModel } from '../model/cubismmodel'; +import { + CSM_ASSERT, + CubismLogDebug, + CubismLogError, + CubismLogWarning +} from '../utils/cubismdebug'; +import { + ACubismMotion, + BeganMotionCallback, + FinishedMotionCallback +} from './acubismmotion'; +import { + CubismMotionCurve, + CubismMotionCurveTarget, + CubismMotionData, + CubismMotionEvent, + CubismMotionPoint, + CubismMotionSegment, + CubismMotionSegmentType +} from './cubismmotioninternal'; +import { CubismMotionJson, EvaluationOptionFlag } from './cubismmotionjson'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; + +const EffectNameEyeBlink = 'EyeBlink'; +const EffectNameLipSync = 'LipSync'; +const TargetNameModel = 'Model'; +const TargetNameParameter = 'Parameter'; +const TargetNamePartOpacity = 'PartOpacity'; + +// Id +const IdNameOpacity = 'Opacity'; + +/** + * Cubism SDK R2 以前のモーションを再現させるなら true 、アニメータのモーションを正しく再現するなら false 。 + */ +const UseOldBeziersCurveMotion = false; + +function lerpPoints( + a: CubismMotionPoint, + b: CubismMotionPoint, + t: number +): CubismMotionPoint { + const result: CubismMotionPoint = new CubismMotionPoint(); + + result.time = a.time + (b.time - a.time) * t; + result.value = a.value + (b.value - a.value) * t; + + return result; +} + +function linearEvaluate(points: CubismMotionPoint[], time: number): number { + let t: number = (time - points[0].time) / (points[1].time - points[0].time); + + if (t < 0.0) { + t = 0.0; + } + + return points[0].value + (points[1].value - points[0].value) * t; +} + +function bezierEvaluate(points: CubismMotionPoint[], time: number): number { + let t: number = (time - points[0].time) / (points[3].time - points[0].time); + + if (t < 0.0) { + t = 0.0; + } + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; +} + +function bezierEvaluateBinarySearch( + points: CubismMotionPoint[], + time: number +): number { + const xError = 0.01; + + const x: number = time; + let x1: number = points[0].time; + let x2: number = points[3].time; + let cx1: number = points[1].time; + let cx2: number = points[2].time; + + let ta = 0.0; + let tb = 1.0; + let t = 0.0; + let i = 0; + + for (let var33 = true; i < 20; ++i) { + if (x < x1 + xError) { + t = ta; + break; + } + + if (x2 - xError < x) { + t = tb; + break; + } + + let centerx: number = (cx1 + cx2) * 0.5; + cx1 = (x1 + cx1) * 0.5; + cx2 = (x2 + cx2) * 0.5; + const ctrlx12: number = (cx1 + centerx) * 0.5; + const ctrlx21: number = (cx2 + centerx) * 0.5; + centerx = (ctrlx12 + ctrlx21) * 0.5; + if (x < centerx) { + tb = (ta + tb) * 0.5; + if (centerx - xError < x) { + t = tb; + break; + } + + x2 = centerx; + cx2 = ctrlx12; + } else { + ta = (ta + tb) * 0.5; + if (x < centerx + xError) { + t = ta; + break; + } + + x1 = centerx; + cx1 = ctrlx21; + } + } + + if (i == 20) { + t = (ta + tb) * 0.5; + } + + if (t < 0.0) { + t = 0.0; + } + if (t > 1.0) { + t = 1.0; + } + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; +} + +function bezierEvaluateCardanoInterpretation( + points: CubismMotionPoint[], + time: number +): number { + const x: number = time; + const x1: number = points[0].time; + const x2: number = points[3].time; + const cx1: number = points[1].time; + const cx2: number = points[2].time; + + const a: number = x2 - 3.0 * cx2 + 3.0 * cx1 - x1; + const b: number = 3.0 * cx2 - 6.0 * cx1 + 3.0 * x1; + const c: number = 3.0 * cx1 - 3.0 * x1; + const d: number = x1 - x; + + const t: number = CubismMath.cardanoAlgorithmForBezier(a, b, c, d); + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; +} + +function steppedEvaluate(points: CubismMotionPoint[], time: number): number { + return points[0].value; +} + +function inverseSteppedEvaluate( + points: CubismMotionPoint[], + time: number +): number { + return points[1].value; +} + +function evaluateCurve( + motionData: CubismMotionData, + index: number, + time: number, + isCorrection: boolean, + endTime: number +): number { + // Find segment to evaluate. + const curve: CubismMotionCurve = motionData.curves[index]; + + let target = -1; + const totalSegmentCount: number = curve.baseSegmentIndex + curve.segmentCount; + let pointPosition = 0; + for (let i: number = curve.baseSegmentIndex; i < totalSegmentCount; ++i) { + // Get first point of next segment. + pointPosition = + motionData.segments[i].basePointIndex + + ((motionData.segments[i].segmentType as CubismMotionSegmentType) == + CubismMotionSegmentType.CubismMotionSegmentType_Bezier + ? 3 + : 1); + + // Break if time lies within current segment. + if (motionData.points[pointPosition].time > time) { + target = i; + break; + } + } + + if (target == -1) { + if (isCorrection && time < endTime) { + return correctEndPoint( + motionData, + totalSegmentCount - 1, + motionData.segments[curve.baseSegmentIndex].basePointIndex, + pointPosition, + time, + endTime + ); + } + return motionData.points[pointPosition].value; + } + + const segment: CubismMotionSegment = motionData.segments[target]; + + return segment.evaluate( + motionData.points.slice(segment.basePointIndex), + time + ); +} + +/** + * 終点から始点への補正処理 + * @param motionData + * @param segmentIndex + * @param beginIndex + * @param endIndex + * @param time + * @param endTime + * @return + */ +function correctEndPoint( + motionData: CubismMotionData, + segmentIndex: number, + beginIndex: number, + endIndex: number, + time: number, + endTime: number +): number { + const motionPoint: CubismMotionPoint[] = [ + new CubismMotionPoint(), + new CubismMotionPoint() + ]; + { + const src = motionData.points[endIndex]; + motionPoint[0].time = src.time; + motionPoint[0].value = src.value; + } + { + const src = motionData.points[beginIndex]; + motionPoint[1].time = endTime; + motionPoint[1].value = src.value; + } + + switch ( + motionData.segments[segmentIndex].segmentType as CubismMotionSegmentType + ) { + case CubismMotionSegmentType.CubismMotionSegmentType_Linear: + case CubismMotionSegmentType.CubismMotionSegmentType_Bezier: + default: + return linearEvaluate(motionPoint, time); + case CubismMotionSegmentType.CubismMotionSegmentType_Stepped: + return steppedEvaluate(motionPoint, time); + case CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped: + return inverseSteppedEvaluate(motionPoint, time); + } +} + +/** + * Enumerator for version control of Motion Behavior. + * For details, see the SDK Manual. + */ +export enum MotionBehavior { + MotionBehavior_V1, + MotionBehavior_V2 +} + +/** + * モーションクラス + * + * モーションのクラス。 + */ +export class CubismMotion extends ACubismMotion { + /** + * インスタンスを作成する + * + * @param buffer motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @param onFinishedMotionHandler モーション再生終了時に呼び出されるコールバック関数 + * @param onBeganMotionHandler モーション再生開始時に呼び出されるコールバック関数 + * @param shouldCheckMotionConsistency motion3.json整合性チェックするかどうか + * @return 作成されたインスタンス + */ + public static create( + buffer: ArrayBuffer, + size: number, + onFinishedMotionHandler?: FinishedMotionCallback, + onBeganMotionHandler?: BeganMotionCallback, + shouldCheckMotionConsistency: boolean = false + ): CubismMotion { + const ret = new CubismMotion(); + + ret.parse(buffer, size, shouldCheckMotionConsistency); + if (ret._motionData) { + ret._sourceFrameRate = ret._motionData.fps; + ret._loopDurationSeconds = ret._motionData.duration; + ret._onFinishedMotion = onFinishedMotionHandler; + ret._onBeganMotion = onBeganMotionHandler; + } else { + csmDelete(ret); + return null; + } + + // NOTE: Editorではループありのモーション書き出しは非対応 + // ret->_loop = (ret->_motionData->Loop > 0); + return ret; + } + + /** + * モデルのパラメータの更新の実行 + * @param model 対象のモデル + * @param userTimeSeconds 現在の時刻[秒] + * @param fadeWeight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + */ + public doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + fadeWeight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void { + if (this._modelCurveIdEyeBlink == null) { + this._modelCurveIdEyeBlink = + CubismFramework.getIdManager().getId(EffectNameEyeBlink); + } + + if (this._modelCurveIdLipSync == null) { + this._modelCurveIdLipSync = + CubismFramework.getIdManager().getId(EffectNameLipSync); + } + + if (this._modelCurveIdOpacity == null) { + this._modelCurveIdOpacity = + CubismFramework.getIdManager().getId(IdNameOpacity); + } + + if (this._motionBehavior === MotionBehavior.MotionBehavior_V2) { + if (this._previousLoopState !== this._isLoop) { + // 終了時間を計算する + this.adjustEndTime(motionQueueEntry); + this._previousLoopState = this._isLoop; + } + } + + let timeOffsetSeconds: number = + userTimeSeconds - motionQueueEntry.getStartTime(); + + if (timeOffsetSeconds < 0.0) { + timeOffsetSeconds = 0.0; // エラー回避 + } + + let lipSyncValue: number = Number.MAX_VALUE; + let eyeBlinkValue: number = Number.MAX_VALUE; + + //まばたき、リップシンクのうちモーションの適用を検出するためのビット(maxFlagCount個まで + const maxTargetSize = 64; + let lipSyncFlags = 0; + let eyeBlinkFlags = 0; + + //瞬き、リップシンクのターゲット数が上限を超えている場合 + if (this._eyeBlinkParameterIds.length > maxTargetSize) { + CubismLogDebug( + 'too many eye blink targets : {0}', + this._eyeBlinkParameterIds.length + ); + } + if (this._lipSyncParameterIds.length > maxTargetSize) { + CubismLogDebug( + 'too many lip sync targets : {0}', + this._lipSyncParameterIds.length + ); + } + + const tmpFadeIn: number = + this._fadeInSeconds <= 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + this._fadeInSeconds + ); + + const tmpFadeOut: number = + this._fadeOutSeconds <= 0.0 || motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + this._fadeOutSeconds + ); + let value: number; + let c: number, parameterIndex: number; + + // 'Repeat' time as necessary. + let time: number = timeOffsetSeconds; + let duration: number = this._motionData.duration; + const isCorrection: boolean = + this._motionBehavior === MotionBehavior.MotionBehavior_V2 && this._isLoop; + + if (this._isLoop) { + if (this._motionBehavior === MotionBehavior.MotionBehavior_V2) { + duration += 1.0 / this._motionData.fps; + } + while (time > duration) { + time -= duration; + } + } + + const curves: Array = this._motionData.curves; + + // Evaluate model curves. + for ( + c = 0; + c < this._motionData.curveCount && + curves[c].type == CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + ++c + ) { + // Evaluate curve and call handler. + value = evaluateCurve(this._motionData, c, time, isCorrection, duration); + + if (curves[c].id == this._modelCurveIdEyeBlink) { + eyeBlinkValue = value; + } else if (curves[c].id == this._modelCurveIdLipSync) { + lipSyncValue = value; + } else if (curves[c].id == this._modelCurveIdOpacity) { + this._modelOpacity = value; + model.setModelOapcity(this.getModelOpacityValue()); + } + } + + let parameterMotionCurveCount = 0; + + for ( + ; + c < this._motionData.curveCount && + curves[c].type == + CubismMotionCurveTarget.CubismMotionCurveTarget_Parameter; + ++c + ) { + parameterMotionCurveCount++; + + // Find parameter index. + parameterIndex = model.getParameterIndex(curves[c].id); + + // Skip curve evaluation if no value in sink. + if (parameterIndex == -1) { + continue; + } + + const sourceValue: number = + model.getParameterValueByIndex(parameterIndex); + + // Evaluate curve and apply value. + value = evaluateCurve(this._motionData, c, time, isCorrection, duration); + + if (eyeBlinkValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._eyeBlinkParameterIds.length && i < maxTargetSize; + ++i + ) { + if (this._eyeBlinkParameterIds[i] == curves[c].id) { + value *= eyeBlinkValue; + eyeBlinkFlags |= 1 << i; + break; + } + } + } + + if (lipSyncValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._lipSyncParameterIds.length && i < maxTargetSize; + ++i + ) { + if (this._lipSyncParameterIds[i] == curves[c].id) { + value += lipSyncValue; + lipSyncFlags |= 1 << i; + break; + } + } + } + + // Process "repeats only" for compatibility + if (model.isRepeat(parameterIndex)) { + value = model.getParameterRepeatValue(parameterIndex, value); + } + + let v: number; + + // パラメータごとのフェード + if (curves[c].fadeInTime < 0.0 && curves[c].fadeOutTime < 0.0) { + // モーションのフェードを適用 + v = sourceValue + (value - sourceValue) * fadeWeight; + } else { + // パラメータに対してフェードインかフェードアウトが設定してある場合はそちらを適用 + let fin: number; + let fout: number; + + if (curves[c].fadeInTime < 0.0) { + fin = tmpFadeIn; + } else { + fin = + curves[c].fadeInTime == 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + curves[c].fadeInTime + ); + } + + if (curves[c].fadeOutTime < 0.0) { + fout = tmpFadeOut; + } else { + fout = + curves[c].fadeOutTime == 0.0 || motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + curves[c].fadeOutTime + ); + } + + const paramWeight: number = this._weight * fin * fout; + + // パラメータごとのフェードを適用 + v = sourceValue + (value - sourceValue) * paramWeight; + } + + model.setParameterValueByIndex(parameterIndex, v, 1.0); + } + + { + if (eyeBlinkValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._eyeBlinkParameterIds.length && i < maxTargetSize; + ++i + ) { + const sourceValue: number = model.getParameterValueById( + this._eyeBlinkParameterIds[i] + ); + + // モーションでの上書きがあった時にはまばたきは適用しない + if ((eyeBlinkFlags >> i) & 0x01) { + continue; + } + + const v: number = + sourceValue + (eyeBlinkValue - sourceValue) * fadeWeight; + + model.setParameterValueById(this._eyeBlinkParameterIds[i], v); + } + } + + if (lipSyncValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._lipSyncParameterIds.length && i < maxTargetSize; + ++i + ) { + const sourceValue: number = model.getParameterValueById( + this._lipSyncParameterIds[i] + ); + + // モーションでの上書きがあった時にはリップシンクは適用しない + if ((lipSyncFlags >> i) & 0x01) { + continue; + } + + const v: number = + sourceValue + (lipSyncValue - sourceValue) * fadeWeight; + + model.setParameterValueById(this._lipSyncParameterIds[i], v); + } + } + } + + for ( + ; + c < this._motionData.curveCount && + curves[c].type == + CubismMotionCurveTarget.CubismMotionCurveTarget_PartOpacity; + ++c + ) { + // Find parameter index. + parameterIndex = model.getParameterIndex(curves[c].id); + + // Skip curve evaluation if no value in sink. + if (parameterIndex == -1) { + continue; + } + + // Evaluate curve and apply value. + value = evaluateCurve(this._motionData, c, time, isCorrection, duration); + + model.setParameterValueByIndex(parameterIndex, value); + } + + if (timeOffsetSeconds >= duration) { + if (this._isLoop) { + this.updateForNextLoop(motionQueueEntry, userTimeSeconds, time); + } else { + if (this._onFinishedMotion) { + this._onFinishedMotion(this); + } + + motionQueueEntry.setIsFinished(true); + } + } + this._lastWeight = fadeWeight; + } + + /** + * Sets the version of the Motion Behavior. + * + * @param Specifies the version of the Motion Behavior. + */ + public setMotionBehavior(motionBehavior: MotionBehavior) { + this._motionBehavior = motionBehavior; + } + + /** + * Gets the version of the Motion Behavior. + * + * @return Returns the version of the Motion Behavior. + */ + public getMotionBehavior(): MotionBehavior { + return this._motionBehavior; + } + + /** + * モーションの長さを取得する。 + * + * @return モーションの長さ[秒] + */ + public getDuration(): number { + return this._isLoop ? -1.0 : this._loopDurationSeconds; + } + + /** + * モーションのループ時の長さを取得する。 + * + * @return モーションのループ時の長さ[秒] + */ + public getLoopDuration(): number { + return this._loopDurationSeconds; + } + + /** + * パラメータに対するフェードインの時間を設定する。 + * + * @param parameterId パラメータID + * @param value フェードインにかかる時間[秒] + */ + public setParameterFadeInTime( + parameterId: CubismIdHandle, + value: number + ): void { + const curves: Array = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves[i].id) { + curves[i].fadeInTime = value; + return; + } + } + } + + /** + * パラメータに対するフェードアウトの時間の設定 + * @param parameterId パラメータID + * @param value フェードアウトにかかる時間[秒] + */ + public setParameterFadeOutTime( + parameterId: CubismIdHandle, + value: number + ): void { + const curves: Array = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves[i].id) { + curves[i].fadeOutTime = value; + return; + } + } + } + + /** + * パラメータに対するフェードインの時間の取得 + * @param parameterId パラメータID + * @return フェードインにかかる時間[秒] + */ + public getParameterFadeInTime(parameterId: CubismIdHandle): number { + const curves: Array = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves[i].id) { + return curves[i].fadeInTime; + } + } + + return -1; + } + + /** + * パラメータに対するフェードアウトの時間を取得 + * + * @param parameterId パラメータID + * @return フェードアウトにかかる時間[秒] + */ + public getParameterFadeOutTime(parameterId: CubismIdHandle): number { + const curves: Array = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves[i].id) { + return curves[i].fadeOutTime; + } + } + + return -1; + } + + /** + * 自動エフェクトがかかっているパラメータIDリストの設定 + * @param eyeBlinkParameterIds 自動まばたきがかかっているパラメータIDのリスト + * @param lipSyncParameterIds リップシンクがかかっているパラメータIDのリスト + */ + public setEffectIds( + eyeBlinkParameterIds: Array, + lipSyncParameterIds: Array + ): void { + this._eyeBlinkParameterIds = eyeBlinkParameterIds; + this._lipSyncParameterIds = lipSyncParameterIds; + } + + /** + * コンストラクタ + */ + public constructor() { + super(); + this._sourceFrameRate = 30.0; + this._loopDurationSeconds = -1.0; + this._isLoop = false; // trueから false へデフォルトを変更 + this._isLoopFadeIn = true; // ループ時にフェードインが有効かどうかのフラグ + this._lastWeight = 0.0; + this._motionData = null; + this._modelCurveIdEyeBlink = null; + this._modelCurveIdLipSync = null; + this._modelCurveIdOpacity = null; + this._eyeBlinkParameterIds = null; + this._lipSyncParameterIds = null; + this._modelOpacity = 1.0; + this._debugMode = false; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._motionData = void 0; + this._motionData = null; + } + + /** + * + * @param motionQueueEntry + * @param userTimeSeconds + * @param time + */ + public updateForNextLoop( + motionQueueEntry: CubismMotionQueueEntry, + userTimeSeconds: number, + time: number + ) { + switch (this._motionBehavior) { + case MotionBehavior.MotionBehavior_V2: + default: + motionQueueEntry.setStartTime(userTimeSeconds - time); // 最初の状態へ + if (this._isLoopFadeIn) { + // ループ中でループ用フェードインが有効のときは、フェードイン設定し直し + motionQueueEntry.setFadeInStartTime(userTimeSeconds - time); + } + + if (this._onFinishedMotion != null) { + this._onFinishedMotion(this); + } + break; + case MotionBehavior.MotionBehavior_V1: + // 旧ループ処理 + motionQueueEntry.setStartTime(userTimeSeconds); // 最初の状態へ + if (this._isLoopFadeIn) { + // ループ中でループ用フェードインが有効のときは、フェードイン設定し直し + motionQueueEntry.setFadeInStartTime(userTimeSeconds); + } + break; + } + } + + /** + * motion3.jsonをパースする。 + * + * @param motionJson motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @param shouldCheckMotionConsistency motion3.json整合性チェックするかどうか + */ + public parse( + motionJson: ArrayBuffer, + size: number, + shouldCheckMotionConsistency: boolean = false + ): void { + let json: CubismMotionJson = new CubismMotionJson(motionJson, size); + + if (!json) { + json.release(); + json = void 0; + return; + } + + if (shouldCheckMotionConsistency) { + const consistency = json.hasConsistency(); + if (!consistency) { + json.release(); + CubismLogError('Inconsistent motion3.json.'); + return; + } + } + + this._motionData = new CubismMotionData(); + + this._motionData.duration = json.getMotionDuration(); + this._motionData.loop = json.isMotionLoop(); + this._motionData.curveCount = json.getMotionCurveCount(); + this._motionData.fps = json.getMotionFps(); + this._motionData.eventCount = json.getEventCount(); + + const areBeziersRestructed: boolean = json.getEvaluationOptionFlag( + EvaluationOptionFlag.EvaluationOptionFlag_AreBeziersRistricted + ); + + if (json.isExistMotionFadeInTime()) { + this._fadeInSeconds = + json.getMotionFadeInTime() < 0.0 ? 1.0 : json.getMotionFadeInTime(); + } else { + this._fadeInSeconds = 1.0; + } + + if (json.isExistMotionFadeOutTime()) { + this._fadeOutSeconds = + json.getMotionFadeOutTime() < 0.0 ? 1.0 : json.getMotionFadeOutTime(); + } else { + this._fadeOutSeconds = 1.0; + } + + updateSize( + this._motionData.curves, + this._motionData.curveCount, + CubismMotionCurve, + true + ); + updateSize( + this._motionData.segments, + json.getMotionTotalSegmentCount(), + CubismMotionSegment, + true + ); + updateSize( + this._motionData.points, + json.getMotionTotalPointCount(), + CubismMotionPoint, + true + ); + updateSize( + this._motionData.events, + this._motionData.eventCount, + CubismMotionEvent, + true + ); + + let totalPointCount = 0; + let totalSegmentCount = 0; + + // Curves + for ( + let curveCount = 0; + curveCount < this._motionData.curveCount; + ++curveCount + ) { + if (json.getMotionCurveTarget(curveCount) == TargetNameModel) { + this._motionData.curves[curveCount].type = + CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + } else if (json.getMotionCurveTarget(curveCount) == TargetNameParameter) { + this._motionData.curves[curveCount].type = + CubismMotionCurveTarget.CubismMotionCurveTarget_Parameter; + } else if ( + json.getMotionCurveTarget(curveCount) == TargetNamePartOpacity + ) { + this._motionData.curves[curveCount].type = + CubismMotionCurveTarget.CubismMotionCurveTarget_PartOpacity; + } else { + CubismLogWarning( + 'Warning : Unable to get segment type from Curve! The number of "CurveCount" may be incorrect!' + ); + } + + this._motionData.curves[curveCount].id = + json.getMotionCurveId(curveCount); + + this._motionData.curves[curveCount].baseSegmentIndex = totalSegmentCount; + + this._motionData.curves[curveCount].fadeInTime = + json.isExistMotionCurveFadeInTime(curveCount) + ? json.getMotionCurveFadeInTime(curveCount) + : -1.0; + this._motionData.curves[curveCount].fadeOutTime = + json.isExistMotionCurveFadeOutTime(curveCount) + ? json.getMotionCurveFadeOutTime(curveCount) + : -1.0; + + // Segments + for ( + let segmentPosition = 0; + segmentPosition < json.getMotionCurveSegmentCount(curveCount); + ) { + if (segmentPosition == 0) { + this._motionData.segments[totalSegmentCount].basePointIndex = + totalPointCount; + + this._motionData.points[totalPointCount].time = + json.getMotionCurveSegment(curveCount, segmentPosition); + this._motionData.points[totalPointCount].value = + json.getMotionCurveSegment(curveCount, segmentPosition + 1); + + totalPointCount += 1; + segmentPosition += 2; + } else { + this._motionData.segments[totalSegmentCount].basePointIndex = + totalPointCount - 1; + } + + const segment: number = json.getMotionCurveSegment( + curveCount, + segmentPosition + ); + + const segmentType: CubismMotionSegmentType = segment; + switch (segmentType) { + case CubismMotionSegmentType.CubismMotionSegmentType_Linear: { + this._motionData.segments[totalSegmentCount].segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Linear; + this._motionData.segments[totalSegmentCount].evaluate = + linearEvaluate; + + this._motionData.points[totalPointCount].time = + json.getMotionCurveSegment(curveCount, segmentPosition + 1); + this._motionData.points[totalPointCount].value = + json.getMotionCurveSegment(curveCount, segmentPosition + 2); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + case CubismMotionSegmentType.CubismMotionSegmentType_Bezier: { + this._motionData.segments[totalSegmentCount].segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Bezier; + + if (areBeziersRestructed || UseOldBeziersCurveMotion) { + this._motionData.segments[totalSegmentCount].evaluate = + bezierEvaluate; + } else { + this._motionData.segments[totalSegmentCount].evaluate = + bezierEvaluateCardanoInterpretation; + } + + this._motionData.points[totalPointCount].time = + json.getMotionCurveSegment(curveCount, segmentPosition + 1); + this._motionData.points[totalPointCount].value = + json.getMotionCurveSegment(curveCount, segmentPosition + 2); + + this._motionData.points[totalPointCount + 1].time = + json.getMotionCurveSegment(curveCount, segmentPosition + 3); + this._motionData.points[totalPointCount + 1].value = + json.getMotionCurveSegment(curveCount, segmentPosition + 4); + + this._motionData.points[totalPointCount + 2].time = + json.getMotionCurveSegment(curveCount, segmentPosition + 5); + this._motionData.points[totalPointCount + 2].value = + json.getMotionCurveSegment(curveCount, segmentPosition + 6); + + totalPointCount += 3; + segmentPosition += 7; + + break; + } + + case CubismMotionSegmentType.CubismMotionSegmentType_Stepped: { + this._motionData.segments[totalSegmentCount].segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Stepped; + this._motionData.segments[totalSegmentCount].evaluate = + steppedEvaluate; + + this._motionData.points[totalPointCount].time = + json.getMotionCurveSegment(curveCount, segmentPosition + 1); + this._motionData.points[totalPointCount].value = + json.getMotionCurveSegment(curveCount, segmentPosition + 2); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + + case CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped: { + this._motionData.segments[totalSegmentCount].segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped; + this._motionData.segments[totalSegmentCount].evaluate = + inverseSteppedEvaluate; + + this._motionData.points[totalPointCount].time = + json.getMotionCurveSegment(curveCount, segmentPosition + 1); + this._motionData.points[totalPointCount].value = + json.getMotionCurveSegment(curveCount, segmentPosition + 2); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + default: { + CSM_ASSERT(0); + break; + } + } + + ++this._motionData.curves[curveCount].segmentCount; + ++totalSegmentCount; + } + } + + for ( + let userdatacount = 0; + userdatacount < json.getEventCount(); + ++userdatacount + ) { + this._motionData.events[userdatacount].fireTime = + json.getEventTime(userdatacount); + this._motionData.events[userdatacount].value = + json.getEventValue(userdatacount); + } + + json.release(); + json = void 0; + json = null; + } + + /** + * モデルのパラメータ更新 + * + * イベント発火のチェック。 + * 入力する時間は呼ばれるモーションタイミングを0とした秒数で行う。 + * + * @param beforeCheckTimeSeconds 前回のイベントチェック時間[秒] + * @param motionTimeSeconds 今回の再生時間[秒] + */ + public getFiredEvent( + beforeCheckTimeSeconds: number, + motionTimeSeconds: number + ): Array { + updateSize(this._firedEventValues, 0); + + // イベントの発火チェック + for (let u = 0; u < this._motionData.eventCount; ++u) { + if ( + this._motionData.events[u].fireTime > beforeCheckTimeSeconds && + this._motionData.events[u].fireTime <= motionTimeSeconds + ) { + this._firedEventValues.push(this._motionData.events[u].value); + } + } + + return this._firedEventValues; + } + + /** + * 透明度のカーブが存在するかどうかを確認する + * + * @return true -> キーが存在する + * false -> キーが存在しない + */ + public isExistModelOpacity(): boolean { + for (let i = 0; i < this._motionData.curveCount; i++) { + const curve: CubismMotionCurve = this._motionData.curves[i]; + + if (curve.type != CubismMotionCurveTarget.CubismMotionCurveTarget_Model) { + continue; + } + + if (curve.id.getString().localeCompare(IdNameOpacity) == 0) { + return true; + } + } + + return false; + } + + /** + * 透明度のカーブのインデックスを返す + * + * @return success:透明度のカーブのインデックス + */ + public getModelOpacityIndex(): number { + if (this.isExistModelOpacity()) { + for (let i = 0; i < this._motionData.curveCount; i++) { + const curve: CubismMotionCurve = this._motionData.curves[i]; + + if ( + curve.type != CubismMotionCurveTarget.CubismMotionCurveTarget_Model + ) { + continue; + } + + if (curve.id.getString().localeCompare(IdNameOpacity) == 0) { + return i; + } + } + } + return -1; + } + + /** + * 透明度のIdを返す + * + * @param index モーションカーブのインデックス + * @return success:透明度のカーブのインデックス + */ + public getModelOpacityId(index: number): CubismIdHandle { + if (index != -1) { + const curve: CubismMotionCurve = this._motionData.curves[index]; + + if (curve.type == CubismMotionCurveTarget.CubismMotionCurveTarget_Model) { + if (curve.id.getString().localeCompare(IdNameOpacity) == 0) { + return CubismFramework.getIdManager().getId(curve.id.getString()); + } + } + } + + return null; + } + + /** + * 現在時間の透明度の値を返す + * + * @return success:モーションの当該時間におけるOpacityの値 + */ + public getModelOpacityValue(): number { + return this._modelOpacity; + } + + /** + * デバッグ用フラグを設定する + * + * @param debugMode デバッグモードの有効・無効 + */ + public setDebugMode(debugMode: boolean): void { + this._debugMode = debugMode; + } + + public _sourceFrameRate: number; // ロードしたファイルのFPS。記述が無ければデフォルト値15fpsとなる + public _loopDurationSeconds: number; // mtnファイルで定義される一連のモーションの長さ + public _motionBehavior: MotionBehavior = MotionBehavior.MotionBehavior_V2; + public _lastWeight: number; // 最後に設定された重み + + public _motionData: CubismMotionData; // 実際のモーションデータ本体 + + public _eyeBlinkParameterIds: Array; // 自動まばたきを適用するパラメータIDハンドルのリスト。 モデル(モデルセッティング)とパラメータを対応付ける。 + public _lipSyncParameterIds: Array; // リップシンクを適用するパラメータIDハンドルのリスト。 モデル(モデルセッティング)とパラメータを対応付ける。 + + public _modelCurveIdEyeBlink: CubismIdHandle; // モデルが持つ自動まばたき用パラメータIDのハンドル。 モデルとモーションを対応付ける。 + public _modelCurveIdLipSync: CubismIdHandle; // モデルが持つリップシンク用パラメータIDのハンドル。 モデルとモーションを対応付ける。 + public _modelCurveIdOpacity: CubismIdHandle; // モデルが持つ不透明度用パラメータIDのハンドル。 モデルとモーションを対応付ける。 + + public _modelOpacity: number; // モーションから取得した不透明度 + + private _debugMode: boolean; // デバッグモードかどうか +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotion'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotion = $.CubismMotion; + export type CubismMotion = $.CubismMotion; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismmotioninternal.ts b/avatar-h5-renderer/framework/src/motion/cubismmotioninternal.ts new file mode 100644 index 0000000..c3ca404 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismmotioninternal.ts @@ -0,0 +1,155 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; + +/** + * @brief モーションカーブの種類 + * + * モーションカーブの種類。 + */ +export enum CubismMotionCurveTarget { + CubismMotionCurveTarget_Model, // モデルに対して + CubismMotionCurveTarget_Parameter, // パラメータに対して + CubismMotionCurveTarget_PartOpacity // パーツの不透明度に対して +} + +/** + * @brief モーションカーブのセグメントの種類 + * + * モーションカーブのセグメントの種類。 + */ +export enum CubismMotionSegmentType { + CubismMotionSegmentType_Linear = 0, // リニア + CubismMotionSegmentType_Bezier = 1, // ベジェ曲線 + CubismMotionSegmentType_Stepped = 2, // ステップ + CubismMotionSegmentType_InverseStepped = 3 // インバースステップ +} + +/** + * @brief モーションカーブの制御点 + * + * モーションカーブの制御点。 + */ +export class CubismMotionPoint { + time = 0.0; // 時間[秒] + value = 0.0; // 値 +} + +/** + * モーションカーブのセグメントの評価関数 + * + * @param points モーションカーブの制御点リスト + * @param time 評価する時間[秒] + */ +export interface csmMotionSegmentEvaluationFunction { + (points: CubismMotionPoint[], time: number): number; +} + +/** + * @brief モーションカーブのセグメント + * + * モーションカーブのセグメント。 + */ +export class CubismMotionSegment { + /** + * @brief コンストラクタ + * + * コンストラクタ。 + */ + public constructor() { + this.evaluate = null; + this.basePointIndex = 0; + this.segmentType = 0; + } + + evaluate: csmMotionSegmentEvaluationFunction; // 使用する評価関数 + basePointIndex: number; // 最初のセグメントへのインデックス + segmentType: number; // セグメントの種類 +} + +/** + * @brief モーションカーブ + * + * モーションカーブ。 + */ +export class CubismMotionCurve { + public constructor() { + this.type = CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + this.segmentCount = 0; + this.baseSegmentIndex = 0; + this.fadeInTime = 0.0; + this.fadeOutTime = 0.0; + } + + type: CubismMotionCurveTarget; // カーブの種類 + id: CubismIdHandle; // カーブのID + segmentCount: number; // セグメントの個数 + baseSegmentIndex: number; // 最初のセグメントのインデックス + fadeInTime: number; // フェードインにかかる時間[秒] + fadeOutTime: number; // フェードアウトにかかる時間[秒] +} + +/** + * イベント。 + */ +export class CubismMotionEvent { + fireTime = 0.0; + value: string; +} + +/** + * @brief モーションデータ + * + * モーションデータ。 + */ +export class CubismMotionData { + public constructor() { + this.duration = 0.0; + this.loop = false; + this.curveCount = 0; + this.eventCount = 0; + this.fps = 0.0; + + this.curves = new Array(); + this.segments = new Array(); + this.points = new Array(); + this.events = new Array(); + } + + duration: number; // モーションの長さ[秒] + loop: boolean; // ループするかどうか + curveCount: number; // カーブの個数 + eventCount: number; // UserDataの個数 + fps: number; // フレームレート + curves: Array; // カーブのリスト + segments: Array; // セグメントのリスト + points: Array; // ポイントのリスト + events: Array; // イベントのリスト +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotioninternal'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionCurve = $.CubismMotionCurve; + export type CubismMotionCurve = $.CubismMotionCurve; + export const CubismMotionCurveTarget = $.CubismMotionCurveTarget; + export type CubismMotionCurveTarget = $.CubismMotionCurveTarget; + export const CubismMotionData = $.CubismMotionData; + export type CubismMotionData = $.CubismMotionData; + export const CubismMotionEvent = $.CubismMotionEvent; + export type CubismMotionEvent = $.CubismMotionEvent; + export const CubismMotionPoint = $.CubismMotionPoint; + export type CubismMotionPoint = $.CubismMotionPoint; + export const CubismMotionSegment = $.CubismMotionSegment; + export type CubismMotionSegment = $.CubismMotionSegment; + export const CubismMotionSegmentType = $.CubismMotionSegmentType; + export type CubismMotionSegmentType = $.CubismMotionSegmentType; + export type csmMotionSegmentEvaluationFunction = + $.csmMotionSegmentEvaluationFunction; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismmotionjson.ts b/avatar-h5-renderer/framework/src/motion/cubismmotionjson.ts new file mode 100644 index 0000000..4d70d11 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismmotionjson.ts @@ -0,0 +1,463 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CSM_ASSERT, CubismLogWarning } from '../utils/cubismdebug'; +import { CubismJson, JsonMap } from '../utils/cubismjson'; +import { CubismMotionSegmentType } from './cubismmotioninternal'; + +// JSON keys +const Meta = 'Meta'; +const Duration = 'Duration'; +const Loop = 'Loop'; +const AreBeziersRestricted = 'AreBeziersRestricted'; +const CurveCount = 'CurveCount'; +const Fps = 'Fps'; +const TotalSegmentCount = 'TotalSegmentCount'; +const TotalPointCount = 'TotalPointCount'; +const Curves = 'Curves'; +const Target = 'Target'; +const Id = 'Id'; +const FadeInTime = 'FadeInTime'; +const FadeOutTime = 'FadeOutTime'; +const Segments = 'Segments'; +const UserData = 'UserData'; +const UserDataCount = 'UserDataCount'; +const TotalUserDataSize = 'TotalUserDataSize'; +const Time = 'Time'; +const Value = 'Value'; + +/** + * motion3.jsonのコンテナ。 + */ +export class CubismMotionJson { + /** + * コンストラクタ + * @param buffer motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * モーションの長さを取得する + * @return モーションの長さ[秒] + */ + public getMotionDuration(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Duration) + .toFloat(); + } + + /** + * モーションのループ情報の取得 + * @return true ループする + * @return false ループしない + */ + public isMotionLoop(): boolean { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Loop) + .toBoolean(); + } + + /** + * motion3.jsonファイルの整合性チェック + * + * @return 正常なファイルの場合はtrueを返す。 + */ + hasConsistency(): boolean { + let result = true; + + if (!this._json || !this._json.getRoot()) { + return false; + } + + const actualCurveListSize = this._json + .getRoot() + .getValueByString(Curves) + .getVector().length; + let actualTotalSegmentCount = 0; + let actualTotalPointCount = 0; + + // カウント処理 + for ( + let curvePosition = 0; + curvePosition < actualCurveListSize; + ++curvePosition + ) { + for ( + let segmentPosition = 0; + segmentPosition < this.getMotionCurveSegmentCount(curvePosition); + ) { + if (segmentPosition == 0) { + actualTotalPointCount += 1; + segmentPosition += 2; + } + + const segment = this.getMotionCurveSegment( + curvePosition, + segmentPosition + ) as CubismMotionSegmentType; + + switch (segment) { + case CubismMotionSegmentType.CubismMotionSegmentType_Linear: + actualTotalPointCount += 1; + segmentPosition += 3; + break; + case CubismMotionSegmentType.CubismMotionSegmentType_Bezier: + actualTotalPointCount += 3; + segmentPosition += 7; + break; + case CubismMotionSegmentType.CubismMotionSegmentType_Stepped: + actualTotalPointCount += 1; + segmentPosition += 3; + break; + case CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped: + actualTotalPointCount += 1; + segmentPosition += 3; + break; + default: + CSM_ASSERT(0); + break; + } + + ++actualTotalSegmentCount; + } + } + + // 個数チェック + if (actualCurveListSize != this.getMotionCurveCount()) { + CubismLogWarning('The number of curves does not match the metadata.'); + result = false; + } + if (actualTotalSegmentCount != this.getMotionTotalSegmentCount()) { + CubismLogWarning('The number of segment does not match the metadata.'); + result = false; + } + if (actualTotalPointCount != this.getMotionTotalPointCount()) { + CubismLogWarning('The number of point does not match the metadata.'); + result = false; + } + + return result; + } + + public getEvaluationOptionFlag(flagType: EvaluationOptionFlag): boolean { + if ( + EvaluationOptionFlag.EvaluationOptionFlag_AreBeziersRistricted == flagType + ) { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(AreBeziersRestricted) + .toBoolean(); + } + + return false; + } + + /** + * モーションカーブの個数の取得 + * @return モーションカーブの個数 + */ + public getMotionCurveCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(CurveCount) + .toInt(); + } + + /** + * モーションのフレームレートの取得 + * @return フレームレート[FPS] + */ + public getMotionFps(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Fps) + .toFloat(); + } + + /** + * モーションのセグメントの総合計の取得 + * @return モーションのセグメントの取得 + */ + public getMotionTotalSegmentCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalSegmentCount) + .toInt(); + } + + /** + * モーションのカーブの制御店の総合計の取得 + * @return モーションのカーブの制御点の総合計 + */ + public getMotionTotalPointCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalPointCount) + .toInt(); + } + + /** + * モーションのフェードイン時間の存在 + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionFadeInTime(): boolean { + return !this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeInTime) + .isNull(); + } + + /** + * モーションのフェードアウト時間の存在 + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionFadeOutTime(): boolean { + return !this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeOutTime) + .isNull(); + } + + /** + * モーションのフェードイン時間の取得 + * @return フェードイン時間[秒] + */ + public getMotionFadeInTime(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeInTime) + .toFloat(); + } + + /** + * モーションのフェードアウト時間の取得 + * @return フェードアウト時間[秒] + */ + public getMotionFadeOutTime(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeOutTime) + .toFloat(); + } + + /** + * モーションのカーブの種類の取得 + * @param curveIndex カーブのインデックス + * @return カーブの種類 + */ + public getMotionCurveTarget(curveIndex: number): string { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Target) + .getRawString(); + } + + /** + * モーションのカーブのIDの取得 + * @param curveIndex カーブのインデックス + * @return カーブのID + */ + public getMotionCurveId(curveIndex: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * モーションのカーブのフェードイン時間の存在 + * @param curveIndex カーブのインデックス + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionCurveFadeInTime(curveIndex: number): boolean { + return !this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeInTime) + .isNull(); + } + + /** + * モーションのカーブのフェードアウト時間の存在 + * @param curveIndex カーブのインデックス + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionCurveFadeOutTime(curveIndex: number): boolean { + return !this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeOutTime) + .isNull(); + } + + /** + * モーションのカーブのフェードイン時間の取得 + * @param curveIndex カーブのインデックス + * @return フェードイン時間[秒] + */ + public getMotionCurveFadeInTime(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeInTime) + .toFloat(); + } + + /** + * モーションのカーブのフェードアウト時間の取得 + * @param curveIndex カーブのインデックス + * @return フェードアウト時間[秒] + */ + public getMotionCurveFadeOutTime(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeOutTime) + .toFloat(); + } + + /** + * モーションのカーブのセグメントの個数を取得する + * @param curveIndex カーブのインデックス + * @return モーションのカーブのセグメントの個数 + */ + public getMotionCurveSegmentCount(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Segments) + .getVector().length; + } + + /** + * モーションのカーブのセグメントの値の取得 + * @param curveIndex カーブのインデックス + * @param segmentIndex セグメントのインデックス + * @return セグメントの値 + */ + public getMotionCurveSegment( + curveIndex: number, + segmentIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Segments) + .getValueByIndex(segmentIndex) + .toFloat(); + } + + /** + * イベントの個数の取得 + * @return イベントの個数 + */ + public getEventCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(UserDataCount) + .toInt(); + } + + /** + * イベントの総文字数の取得 + * @return イベントの総文字数 + */ + public getTotalEventValueSize(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalUserDataSize) + .toInt(); + } + + /** + * イベントの時間の取得 + * @param userDataIndex イベントのインデックス + * @return イベントの時間[秒] + */ + public getEventTime(userDataIndex: number): number { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(userDataIndex) + .getValueByString(Time) + .toFloat(); + } + + /** + * イベントの取得 + * @param userDataIndex イベントのインデックス + * @return イベントの文字列 + */ + public getEventValue(userDataIndex: number): string { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(userDataIndex) + .getValueByString(Value) + .getRawString(); + } + + _json: CubismJson; // motion3.jsonのデータ +} + +/** + * @brief ベジェカーブの解釈方法のフラグタイプ + */ +export enum EvaluationOptionFlag { + EvaluationOptionFlag_AreBeziersRistricted = 0 ///< ベジェハンドルの規制状態 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotionjson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionJson = $.CubismMotionJson; + export type CubismMotionJson = $.CubismMotionJson; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismmotionmanager.ts b/avatar-h5-renderer/framework/src/motion/cubismmotionmanager.ts new file mode 100644 index 0000000..068dee6 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismmotionmanager.ts @@ -0,0 +1,126 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismModel } from '../model/cubismmodel'; +import { ACubismMotion } from './acubismmotion'; +import { + CubismMotionQueueEntryHandle, + CubismMotionQueueManager +} from './cubismmotionqueuemanager'; + +/** + * モーションの管理 + * + * モーションの管理を行うクラス + */ +export class CubismMotionManager extends CubismMotionQueueManager { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._currentPriority = 0; + this._reservePriority = 0; + } + + /** + * 再生中のモーションの優先度の取得 + * @return モーションの優先度 + */ + public getCurrentPriority(): number { + return this._currentPriority; + } + + /** + * 予約中のモーションの優先度を取得する。 + * @return モーションの優先度 + */ + public getReservePriority(): number { + return this._reservePriority; + } + + /** + * 予約中のモーションの優先度を設定する。 + * @param val 優先度 + */ + public setReservePriority(val: number): void { + this._reservePriority = val; + } + + /** + * 優先度を設定してモーションを開始する。 + * + * @param motion モーション + * @param autoDelete 再生が狩猟したモーションのインスタンスを削除するならtrue + * @param priority 優先度 + * @return 開始したモーションの識別番号を返す。個別のモーションが終了したか否かを判定するIsFinished()の引数で使用する。開始できない時は「-1」 + */ + public startMotionPriority( + motion: ACubismMotion, + autoDelete: boolean, + priority: number + ): CubismMotionQueueEntryHandle { + if (priority == this._reservePriority) { + this._reservePriority = 0; // 予約を解除 + } + + this._currentPriority = priority; // 再生中モーションの優先度を設定 + + return super.startMotion(motion, autoDelete); + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する。 + * + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + * @return true 更新されている + * @return false 更新されていない + */ + public updateMotion(model: CubismModel, deltaTimeSeconds: number): boolean { + this._userTimeSeconds += deltaTimeSeconds; + + const updated: boolean = super.doUpdateMotion(model, this._userTimeSeconds); + + if (this.isFinished()) { + this._currentPriority = 0; // 再生中のモーションの優先度を解除 + } + + return updated; + } + + /** + * モーションを予約する。 + * + * @param priority 優先度 + * @return true 予約できた + * @return false 予約できなかった + */ + public reserveMotion(priority: number): boolean { + if ( + priority <= this._reservePriority || + priority <= this._currentPriority + ) { + return false; + } + + this._reservePriority = priority; + + return true; + } + + _currentPriority: number; // 現在再生中のモーションの優先度 + _reservePriority: number; // 再生予定のモーションの優先度。再生中は0になる。モーションファイルを別スレッドで読み込むときの機能。 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotionmanager'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionManager = $.CubismMotionManager; + export type CubismMotionManager = $.CubismMotionManager; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismmotionqueueentry.ts b/avatar-h5-renderer/framework/src/motion/cubismmotionqueueentry.ts new file mode 100644 index 0000000..c714254 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismmotionqueueentry.ts @@ -0,0 +1,262 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ACubismMotion } from './acubismmotion'; +import { CubismMotionQueueEntryHandle } from './cubismmotionqueuemanager'; + +/** + * CubismMotionQueueManagerで再生している各モーションの管理クラス。 + */ +export class CubismMotionQueueEntry { + /** + * コンストラクタ + */ + public constructor() { + this._autoDelete = false; + this._motion = null; + this._available = true; + this._finished = false; + this._started = false; + this._startTimeSeconds = -1.0; + this._fadeInStartTimeSeconds = 0.0; + this._endTimeSeconds = -1.0; + this._stateTimeSeconds = 0.0; + this._stateWeight = 0.0; + this._lastEventCheckSeconds = 0.0; + this._motionQueueEntryHandle = this; + this._fadeOutSeconds = 0.0; + this._isTriggeredFadeOut = false; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._autoDelete && this._motion) { + ACubismMotion.delete(this._motion); // + } + } + + /** + * フェードアウト時間と開始判定の設定 + * @param fadeOutSeconds フェードアウトにかかる時間[秒] + */ + public setFadeOut(fadeOutSeconds: number): void { + this._fadeOutSeconds = fadeOutSeconds; + this._isTriggeredFadeOut = true; + } + + /** + * フェードアウトの開始 + * @param fadeOutSeconds フェードアウトにかかる時間[秒] + * @param userTimeSeconds デルタ時間の積算値[秒] + */ + public startFadeOut(fadeOutSeconds: number, userTimeSeconds: number): void { + const newEndTimeSeconds: number = userTimeSeconds + fadeOutSeconds; + this._isTriggeredFadeOut = true; + + if ( + this._endTimeSeconds < 0.0 || + newEndTimeSeconds < this._endTimeSeconds + ) { + this._endTimeSeconds = newEndTimeSeconds; + } + } + + /** + * モーションの終了の確認 + * + * @return true モーションが終了した + * @return false 終了していない + */ + public isFinished(): boolean { + return this._finished; + } + + /** + * モーションの開始の確認 + * @return true モーションが開始した + * @return false 開始していない + */ + public isStarted(): boolean { + return this._started; + } + + /** + * モーションの開始時刻の取得 + * @return モーションの開始時刻[秒] + */ + public getStartTime(): number { + return this._startTimeSeconds; + } + + /** + * フェードインの開始時刻の取得 + * @return フェードインの開始時刻[秒] + */ + public getFadeInStartTime(): number { + return this._fadeInStartTimeSeconds; + } + + /** + * フェードインの終了時刻の取得 + * @return フェードインの終了時刻の取得 + */ + public getEndTime(): number { + return this._endTimeSeconds; + } + + /** + * モーションの開始時刻の設定 + * @param startTime モーションの開始時刻 + */ + public setStartTime(startTime: number): void { + this._startTimeSeconds = startTime; + } + + /** + * フェードインの開始時刻の設定 + * @param startTime フェードインの開始時刻[秒] + */ + public setFadeInStartTime(startTime: number): void { + this._fadeInStartTimeSeconds = startTime; + } + + /** + * フェードインの終了時刻の設定 + * @param endTime フェードインの終了時刻[秒] + */ + public setEndTime(endTime: number): void { + this._endTimeSeconds = endTime; + } + + /** + * モーションの終了の設定 + * @param f trueならモーションの終了 + */ + public setIsFinished(f: boolean): void { + this._finished = f; + } + + /** + * モーション開始の設定 + * @param f trueならモーションの開始 + */ + public setIsStarted(f: boolean): void { + this._started = f; + } + + /** + * モーションの有効性の確認 + * @return true モーションは有効 + * @return false モーションは無効 + */ + public isAvailable(): boolean { + return this._available; + } + + /** + * モーションの有効性の設定 + * @param v trueならモーションは有効 + */ + public setIsAvailable(v: boolean): void { + this._available = v; + } + + /** + * モーションの状態の設定 + * @param timeSeconds 現在時刻[秒] + * @param weight モーション尾重み + */ + public setState(timeSeconds: number, weight: number): void { + this._stateTimeSeconds = timeSeconds; + this._stateWeight = weight; + } + + /** + * モーションの現在時刻の取得 + * @return モーションの現在時刻[秒] + */ + public getStateTime(): number { + return this._stateTimeSeconds; + } + + /** + * モーションの重みの取得 + * @return モーションの重み + */ + public getStateWeight(): number { + return this._stateWeight; + } + + /** + * 最後にイベントの発火をチェックした時間を取得 + * + * @return 最後にイベントの発火をチェックした時間[秒] + */ + public getLastCheckEventSeconds(): number { + return this._lastEventCheckSeconds; + } + + /** + * 最後にイベントをチェックした時間を設定 + * @param checkSeconds 最後にイベントをチェックした時間[秒] + */ + public setLastCheckEventSeconds(checkSeconds: number): void { + this._lastEventCheckSeconds = checkSeconds; + } + + /** + * フェードアウト開始判定の取得 + * @return フェードアウト開始するかどうか + */ + public isTriggeredFadeOut(): boolean { + return this._isTriggeredFadeOut; + } + + /** + * フェードアウト時間の取得 + * @return フェードアウト時間[秒] + */ + public getFadeOutSeconds(): number { + return this._fadeOutSeconds; + } + + /** + * モーションの取得 + * + * @return モーション + */ + public getCubismMotion(): ACubismMotion { + return this._motion; + } + + _autoDelete: boolean; // 自動削除 + _motion: ACubismMotion; // モーション + + _available: boolean; // 有効化フラグ + _finished: boolean; // 終了フラグ + _started: boolean; // 開始フラグ + _startTimeSeconds: number; // モーション再生開始時刻[秒] + _fadeInStartTimeSeconds: number; // フェードイン開始時刻(ループの時は初回のみ)[秒] + _endTimeSeconds: number; // 終了予定時刻[秒] + _stateTimeSeconds: number; // 時刻の状態[秒] + _stateWeight: number; // 重みの状態 + _lastEventCheckSeconds: number; // 最終のMotion側のチェックした時間 + private _fadeOutSeconds: number; // フェードアウト時間[秒] + private _isTriggeredFadeOut: boolean; // フェードアウト開始フラグ + + _motionQueueEntryHandle: CubismMotionQueueEntryHandle; // インスタンスごとに一意の値を持つ識別番号 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotionqueueentry'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionQueueEntry = $.CubismMotionQueueEntry; + export type CubismMotionQueueEntry = $.CubismMotionQueueEntry; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismmotionqueuemanager.ts b/avatar-h5-renderer/framework/src/motion/cubismmotionqueuemanager.ts new file mode 100644 index 0000000..afcb575 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismmotionqueuemanager.ts @@ -0,0 +1,329 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ACubismMotion } from './acubismmotion'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; +import { CubismModel } from '../model/cubismmodel'; + +/** + * モーション再生の管理 + * + * モーション再生の管理用クラス。CubismMotionモーションなどACubismMotionのサブクラスを再生するために使用する。 + * + * @note 再生中に別のモーションが StartMotion()された場合は、新しいモーションに滑らかに変化し旧モーションは中断する。 + * 表情用モーション、体用モーションなどを分けてモーション化した場合など、 + * 複数のモーションを同時に再生させる場合は、複数のCubismMotionQueueManagerインスタンスを使用する。 + */ +export class CubismMotionQueueManager { + /** + * コンストラクタ + */ + public constructor() { + this._userTimeSeconds = 0.0; + this._eventCallBack = null; + this._eventCustomData = null; + this._motions = new Array(); + } + + /** + * デストラクタ + */ + public release(): void { + for (let i = 0; i < this._motions.length; ++i) { + if (this._motions[i]) { + this._motions[i].release(); + this._motions[i] = null; + } + } + + this._motions = null; + } + + /** + * 指定したモーションの開始 + * + * 指定したモーションを開始する。同じタイプのモーションが既にある場合は、既存のモーションに終了フラグを立て、フェードアウトを開始させる。 + * + * @param motion 開始するモーション + * @param autoDelete 再生が終了したモーションのインスタンスを削除するなら true + * @param userTimeSeconds Deprecated: デルタ時間の積算値[秒] 関数内で参照していないため使用は非推奨。 + * @return 開始したモーションの識別番号を返す。個別のモーションが終了したか否かを判定するIsFinished()の引数で使用する。開始できない時は「-1」 + */ + public startMotion( + motion: ACubismMotion, + autoDelete: boolean, + userTimeSeconds?: number + ): CubismMotionQueueEntryHandle { + if (motion == null) { + return InvalidMotionQueueEntryHandleValue; + } + + let motionQueueEntry: CubismMotionQueueEntry = null; + + // 既にモーションがあれば終了フラグを立てる + for (let i = 0; i < this._motions.length; ++i) { + motionQueueEntry = this._motions[i]; + if (motionQueueEntry == null) { + continue; + } + + motionQueueEntry.setFadeOut(motionQueueEntry._motion.getFadeOutTime()); // フェードアウト設定 + } + + motionQueueEntry = new CubismMotionQueueEntry(); // 終了時に破棄する + motionQueueEntry._autoDelete = autoDelete; + motionQueueEntry._motion = motion; + + this._motions.push(motionQueueEntry); + + return motionQueueEntry._motionQueueEntryHandle; + } + + /** + * 全てのモーションの終了の確認 + * @return true 全て終了している + * @return false 終了していない + */ + public isFinished(): boolean { + // ------- 処理を行う ------- + // 既にモーションがあれば終了フラグを立てる + + for (let i = 0; i < this._motions.length; ) { + let motionQueueEntry: CubismMotionQueueEntry = this._motions[i]; + + if (motionQueueEntry == null) { + this._motions.splice(i, 1); // 削除 + continue; + } + + const motion: ACubismMotion = motionQueueEntry._motion; + + if (motion == null) { + motionQueueEntry.release(); + motionQueueEntry = null; + this._motions.splice(i, 1); // 削除 + continue; + } + + // ----- 終了済みの処理があれば削除する ------ + if (!motionQueueEntry.isFinished()) { + return false; + } else { + i++; + } + } + + return true; + } + + /** + * 指定したモーションの終了の確認 + * @param motionQueueEntryNumber モーションの識別番号 + * @return true 全て終了している + * @return false 終了していない + */ + public isFinishedByHandle( + motionQueueEntryNumber: CubismMotionQueueEntryHandle + ): boolean { + for (let i = 0; i < this._motions.length; i++) { + const motionQueueEntry: CubismMotionQueueEntry = this._motions[i]; + + if (motionQueueEntry == null) { + continue; + } + + if ( + motionQueueEntry._motionQueueEntryHandle == motionQueueEntryNumber && + !motionQueueEntry.isFinished() + ) { + return false; + } + } + return true; + } + + /** + * 全てのモーションを停止する + */ + public stopAllMotions(): void { + // ------- 処理を行う ------- + // 既にモーションがあれば終了フラグを立てる + + for (let i = 0; i < this._motions.length; i++) { + const motionQueueEntry: CubismMotionQueueEntry = this._motions[i]; + + if (motionQueueEntry == null) { + this._motions.splice(i, 1); // 削除 + + continue; + } + + // ----- 終了済みの処理があれば削除する ------ + motionQueueEntry.release(); + this._motions.splice(i, 1); // 削除 + continue; + } + } + + /** + * @brief CubismMotionQueueEntryの配列の取得 + * + * CubismMotionQueueEntryの配列を取得する。 + * + * @return CubismMotionQueueEntryの配列へのポインタ + * NULL 見つからなかった + */ + public getCubismMotionQueueEntries(): Array { + return this._motions; + } + + /** + * 指定したCubismMotionQueueEntryの取得 + + * @param motionQueueEntryNumber モーションの識別番号 + * @return 指定したCubismMotionQueueEntry + * @return null 見つからなかった + */ + public getCubismMotionQueueEntry( + motionQueueEntryNumber: any + ): CubismMotionQueueEntry { + //------- 処理を行う ------- + + for (let i = 0; i < this._motions.length; i++) { + const motionQueueEntry: CubismMotionQueueEntry = this._motions[i]; + + if (motionQueueEntry == null) { + continue; + } + + if (motionQueueEntry._motionQueueEntryHandle == motionQueueEntryNumber) { + return motionQueueEntry; + } + } + + return null; + } + + /** + * イベントを受け取るCallbackの登録 + * + * @param callback コールバック関数 + * @param customData コールバックに返されるデータ + */ + public setEventCallback( + callback: CubismMotionEventFunction, + customData: any = null + ): void { + this._eventCallBack = callback; + this._eventCustomData = customData; + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する。 + * + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @return true モデルへパラメータ値の反映あり + * @return false モデルへパラメータ値の反映なし(モーションの変化なし) + */ + public doUpdateMotion(model: CubismModel, userTimeSeconds: number): boolean { + let updated = false; + + // ------- 処理を行う -------- + // 既にモーションがあれば終了フラグを立てる + + for (let i = 0; i < this._motions.length; ) { + let motionQueueEntry: CubismMotionQueueEntry = this._motions[i]; + + if (motionQueueEntry == null) { + this._motions.splice(i, 1); // 削除 + continue; + } + + const motion: ACubismMotion = motionQueueEntry._motion; + + if (motion == null) { + motionQueueEntry.release(); + motionQueueEntry = null; + this._motions.splice(i, 1); // 削除 + continue; + } + + // ------ 値を反映する ------ + motion.updateParameters(model, motionQueueEntry, userTimeSeconds); + updated = true; + + // ------ ユーザトリガーイベントを検査する ---- + const firedList: Array = motion.getFiredEvent( + motionQueueEntry.getLastCheckEventSeconds() - + motionQueueEntry.getStartTime(), + userTimeSeconds - motionQueueEntry.getStartTime() + ); + + for (let i = 0; i < firedList.length; ++i) { + this._eventCallBack(this, firedList[i], this._eventCustomData); + } + + motionQueueEntry.setLastCheckEventSeconds(userTimeSeconds); + + // ------ 終了済みの処理があれば削除する ------ + if (motionQueueEntry.isFinished()) { + motionQueueEntry.release(); + motionQueueEntry = null; + this._motions.splice(i, 1); // 削除 + } else { + if (motionQueueEntry.isTriggeredFadeOut()) { + motionQueueEntry.startFadeOut( + motionQueueEntry.getFadeOutSeconds(), + userTimeSeconds + ); + } + i++; + } + } + + return updated; + } + _userTimeSeconds: number; // デルタ時間の積算値[秒] + + _motions: Array; // モーション + _eventCallBack: CubismMotionEventFunction; // コールバック関数 + _eventCustomData: any; // コールバックに戻されるデータ +} + +/** + * イベントのコールバック関数を定義 + * + * イベントのコールバックに登録できる関数の型情報 + * @param caller 発火したイベントを再生させたCubismMotionQueueManager + * @param eventValue 発火したイベントの文字列データ + * @param customData コールバックに返される登録時に指定されたデータ + */ +export interface CubismMotionEventFunction { + (caller: CubismMotionQueueManager, eventValue: string, customData: any): void; +} + +/** + * モーションの識別番号 + * + * モーションの識別番号の定義 + */ +export declare type CubismMotionQueueEntryHandle = any; +export const InvalidMotionQueueEntryHandleValue: CubismMotionQueueEntryHandle = + -1; + +// Namespace definition for compatibility. +import * as $ from './cubismmotionqueuemanager'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionQueueManager = $.CubismMotionQueueManager; + export type CubismMotionQueueManager = $.CubismMotionQueueManager; + export const InvalidMotionQueueEntryHandleValue = + $.InvalidMotionQueueEntryHandleValue; + export type CubismMotionQueueEntryHandle = $.CubismMotionQueueEntryHandle; + export type CubismMotionEventFunction = $.CubismMotionEventFunction; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismphysicsupdater.ts b/avatar-h5-renderer/framework/src/motion/cubismphysicsupdater.ts new file mode 100644 index 0000000..3ee0b39 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismphysicsupdater.ts @@ -0,0 +1,60 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismUpdater, CubismUpdateOrder } from './icubismupdater'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismPhysics } from '../physics/cubismphysics'; + +/** + * Updater for physics effects. + * Handles the management of physics simulation through the CubismPhysics class. + */ +export class CubismPhysicsUpdater extends ICubismUpdater { + private _physics: CubismPhysics; + + /** + * Constructor + * + * @param physics CubismPhysics reference + */ + constructor(physics: CubismPhysics); + + /** + * Constructor + * + * @param physics CubismPhysics reference + * @param executionOrder Order of operations + */ + constructor(physics: CubismPhysics, executionOrder: number); + + constructor(physics: CubismPhysics, executionOrder?: number) { + super(executionOrder ?? CubismUpdateOrder.CubismUpdateOrder_Physics); + this._physics = physics; + } + + /** + * Update process. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void { + if (!model) { + return; + } + + this._physics.evaluate(model, deltaTimeSeconds); + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismphysicsupdater'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPhysicsUpdater = $.CubismPhysicsUpdater; + export type CubismPhysicsUpdater = $.CubismPhysicsUpdater; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismposeupdater.ts b/avatar-h5-renderer/framework/src/motion/cubismposeupdater.ts new file mode 100644 index 0000000..8c7f8aa --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismposeupdater.ts @@ -0,0 +1,60 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismUpdater, CubismUpdateOrder } from './icubismupdater'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismPose } from '../effect/cubismpose'; + +/** + * Updater for pose effects. + * Handles the management of pose animation through the CubismPose class. + */ +export class CubismPoseUpdater extends ICubismUpdater { + private _pose: CubismPose; + + /** + * Constructor + * + * @param pose CubismPose reference + */ + constructor(pose: CubismPose); + + /** + * Constructor + * + * @param pose CubismPose reference + * @param executionOrder Order of operations + */ + constructor(pose: CubismPose, executionOrder: number); + + constructor(pose: CubismPose, executionOrder?: number) { + super(executionOrder ?? CubismUpdateOrder.CubismUpdateOrder_Pose); + this._pose = pose; + } + + /** + * Update process. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void { + if (!model) { + return; + } + + this._pose.updateParameters(model, deltaTimeSeconds); + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismposeupdater'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPoseUpdater = $.CubismPoseUpdater; + export type CubismPoseUpdater = $.CubismPoseUpdater; +} diff --git a/avatar-h5-renderer/framework/src/motion/cubismupdatescheduler.ts b/avatar-h5-renderer/framework/src/motion/cubismupdatescheduler.ts new file mode 100644 index 0000000..a78b538 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/cubismupdatescheduler.ts @@ -0,0 +1,180 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismUpdater, ICubismUpdaterChangeListener } from './icubismupdater'; +import { CubismModel } from '../model/cubismmodel'; + +/** + * Scheduler for managing and updating ICubismUpdater instances. + * Handles the management of update order and execution through a sorted list. + */ +export class CubismUpdateScheduler implements ICubismUpdaterChangeListener { + private _cubismUpdatableList: ICubismUpdater[]; + private _needsSort: boolean; + + /** + * Constructor + */ + constructor() { + this._cubismUpdatableList = []; + this._needsSort = false; + } + + /** + * Destructor equivalent - releases all updaters and removes listeners + */ + public release(): void { + // Remove all listeners before clearing + for (const updater of this._cubismUpdatableList) { + if (updater) { + updater.removeChangeListener(this); + } + } + // Clear the list - in TypeScript we don't need to manually delete objects + // as they will be garbage collected when no longer referenced + this._cubismUpdatableList.length = 0; + } + + /** + * Adds ICubismUpdater to the update list. + * The list will be automatically sorted by execution order before the next update. + * + * @param updatable The ICubismUpdater instance to be added. + */ + public addUpdatableList(updatable: ICubismUpdater): void { + if (!updatable) { + return; + } + + // Check for duplicate registration + if (this.hasUpdatable(updatable)) { + return; // Already exists, skip adding + } + + this._cubismUpdatableList.push(updatable); + updatable.addChangeListener(this); + this._needsSort = true; + } + + /** + * Removes ICubismUpdater from the update list. + * + * @param updatable The ICubismUpdater instance to be removed. + * @return true if the updater was found and removed, false otherwise. + */ + public removeUpdatableList(updatable: ICubismUpdater): boolean { + if (!updatable) { + return false; + } + + const index = this._cubismUpdatableList.indexOf(updatable); + if (index >= 0) { + this._cubismUpdatableList.splice(index, 1); + updatable.removeChangeListener(this); + // Note: removal doesn't require re-sorting + return true; + } + return false; + } + + /** + * Sorts the update list using the ICubismUpdater sort function. + */ + public sortUpdatableList(): void { + this._cubismUpdatableList.sort(ICubismUpdater.sortFunction); + this._needsSort = false; + } + + /** + * Updates every element in the list. + * The list is automatically sorted by execution order before execution. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + public onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void { + if (!model) { + return; + } + + // Automatically sort if needed to ensure execution order + if (this._needsSort) { + this.sortUpdatableList(); + } + + for (let i = 0; i < this._cubismUpdatableList.length; ++i) { + const updater = this._cubismUpdatableList[i]; + if (updater) { + updater.onLateUpdate(model, deltaTimeSeconds); + } + } + } + + /** + * Gets the number of updaters in the list. + * + * @return Number of updaters + */ + public getUpdatableCount(): number { + return this._cubismUpdatableList.length; + } + + /** + * Gets the updater at the specified index. + * + * @param index Index of the updater to retrieve + * @return The updater at the specified index, or null if index is out of bounds + */ + public getUpdatable(index: number): ICubismUpdater | null { + if (index < 0 || index >= this._cubismUpdatableList.length) { + return null; + } + return this._cubismUpdatableList[index]; + } + + /** + * Checks if the specified updater exists in the list. + * + * @param updatable The updater to check for + * @return true if the updater exists in the list, false otherwise + */ + public hasUpdatable(updatable: ICubismUpdater): boolean { + return this._cubismUpdatableList.indexOf(updatable) >= 0; + } + + /** + * Clears all updaters from the list. + */ + public clearUpdatableList(): void { + // Remove listeners before clearing + for (const updater of this._cubismUpdatableList) { + if (updater) { + updater.removeChangeListener(this); + } + } + this._cubismUpdatableList.length = 0; + this._needsSort = false; + } + + /** + * Called when an updater's execution order has changed. + * Marks the list for re-sorting. + * + * @param updater The updater that was changed + */ + public onUpdaterChanged(updater: ICubismUpdater): void { + this._needsSort = true; + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismupdatescheduler'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismUpdateScheduler = $.CubismUpdateScheduler; + export type CubismUpdateScheduler = $.CubismUpdateScheduler; +} diff --git a/avatar-h5-renderer/framework/src/motion/icubismupdater.ts b/avatar-h5-renderer/framework/src/motion/icubismupdater.ts new file mode 100644 index 0000000..475febe --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/icubismupdater.ts @@ -0,0 +1,126 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismModel } from '../model/cubismmodel'; + +/** + * Interface for listening to ICubismUpdater changes. + */ +export interface ICubismUpdaterChangeListener { + /** + * Called when an updater's execution order has changed. + * + * @param updater The updater that was changed + */ + onUpdaterChanged(updater: ICubismUpdater): void; +} + +export enum CubismUpdateOrder { + CubismUpdateOrder_EyeBlink = 200, + CubismUpdateOrder_Expression = 300, + CubismUpdateOrder_Drag = 400, + CubismUpdateOrder_Breath = 500, + CubismUpdateOrder_Physics = 600, + CubismUpdateOrder_LipSync = 700, + CubismUpdateOrder_Pose = 800, + CubismUpdateOrder_Max = Number.MAX_SAFE_INTEGER +} + +/** + * Abstract base class for motions.
+ * Handles the management of motion playback through the CubismUpdateScheduler. + */ +export abstract class ICubismUpdater { + /** + * Comparison function used when sorting ICubismUpdater objects. + * + * @param left The first ICubismUpdater object to be compared. + * @param right The second ICubismUpdater object to be compared. + * + * @return negative if left should be placed before right, + * positive if right should be placed before left, + * zero if they are equal. + */ + static sortFunction(left: ICubismUpdater, right: ICubismUpdater): number { + if (!left || !right) { + if (!left && !right) return 0; + if (!left) return 1; // null/undefined elements go to end + if (!right) return -1; + } + return left.getExecutionOrder() - right.getExecutionOrder(); + } + + private _executionOrder: number; + private _changeListeners: ICubismUpdaterChangeListener[] = []; + + /** + * Constructor + */ + constructor(executionOrder: number = 0) { + this._executionOrder = executionOrder; + } + + /** + * Update process. + * + * @param model Model to update + * @param deltaTimeSeconds Delta time in seconds. + */ + abstract onLateUpdate(model: CubismModel, deltaTimeSeconds: number): void; + + getExecutionOrder(): number { + return this._executionOrder; + } + + setExecutionOrder(executionOrder: number): void { + if (this._executionOrder !== executionOrder) { + this._executionOrder = executionOrder; + this.notifyChangeListeners(); + } + } + + /** + * Adds a listener to be notified when this updater's properties change. + * + * @param listener The listener to add + */ + addChangeListener(listener: ICubismUpdaterChangeListener): void { + if (listener && this._changeListeners.indexOf(listener) === -1) { + this._changeListeners.push(listener); + } + } + + /** + * Removes a listener from the notification list. + * + * @param listener The listener to remove + */ + removeChangeListener(listener: ICubismUpdaterChangeListener): void { + const index = this._changeListeners.indexOf(listener); + if (index >= 0) { + this._changeListeners.splice(index, 1); + } + } + + /** + * Notifies all registered listeners that this updater has changed. + */ + private notifyChangeListeners(): void { + for (const listener of this._changeListeners) { + listener.onUpdaterChanged(this); + } + } +} + +// Namespace definition for compatibility. +import * as $ from './icubismupdater'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const ICubismUpdater = $.ICubismUpdater; + export type ICubismUpdater = $.ICubismUpdater; + export type ICubismUpdaterChangeListener = $.ICubismUpdaterChangeListener; +} diff --git a/avatar-h5-renderer/framework/src/motion/iparameterprovider.ts b/avatar-h5-renderer/framework/src/motion/iparameterprovider.ts new file mode 100644 index 0000000..62e2827 --- /dev/null +++ b/avatar-h5-renderer/framework/src/motion/iparameterprovider.ts @@ -0,0 +1,41 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * Interface class for providing parameter values.
+ * Defines the base interface for classes that supply parameter values to the model. + */ +export abstract class IParameterProvider { + /** + * Constructor + */ + constructor() {} + + /** + * Update process. + * + * @param deltaTimeSeconds Delta time in seconds (optional). + * + * @return true if the update is successful. + */ + abstract update(deltaTimeSeconds?: number): boolean; + + /** + * Retrieves the current value of the parameter. + * + * @return The parameter value as a floating-point number. + */ + abstract getParameter(): number; +} + +// Namespace definition for compatibility. +import * as $ from './iparameterprovider'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const IParameterProvider = $.IParameterProvider; + export type IParameterProvider = $.IParameterProvider; +} diff --git a/avatar-h5-renderer/framework/src/physics/cubismphysics.ts b/avatar-h5-renderer/framework/src/physics/cubismphysics.ts new file mode 100644 index 0000000..e1efd44 --- /dev/null +++ b/avatar-h5-renderer/framework/src/physics/cubismphysics.ts @@ -0,0 +1,1358 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from '../math/cubismmath'; +import { CubismVector2 } from '../math/cubismvector2'; +import { CubismModel } from '../model/cubismmodel'; +import { updateSize } from '../utils/cubismarrayutils'; +import { + CubismPhysicsInput, + CubismPhysicsNormalization, + CubismPhysicsOutput, + CubismPhysicsParticle, + CubismPhysicsRig, + CubismPhysicsSource, + CubismPhysicsSubRig, + CubismPhysicsTargetType +} from './cubismphysicsinternal'; +import { CubismPhysicsJson } from './cubismphysicsjson'; + +// physics types tags. +const PhysicsTypeTagX = 'X'; +const PhysicsTypeTagY = 'Y'; +const PhysicsTypeTagAngle = 'Angle'; + +// Constant of air resistance. +const AirResistance = 5.0; + +// Constant of maximum weight of input and output ratio. +const MaximumWeight = 100.0; + +// Constant of threshold of movement. +const MovementThreshold = 0.001; + +// Constant of maximum allowed delta time +const MaxDeltaTime = 5.0; + +/** + * 物理演算クラス + */ +export class CubismPhysics { + /** + * インスタンスの作成 + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create(buffer: ArrayBuffer, size: number): CubismPhysics { + const ret: CubismPhysics = new CubismPhysics(); + + ret.parse(buffer, size); + ret._physicsRig.gravity.y = 0; + + return ret; + } + + /** + * インスタンスを破棄する + * @param physics 破棄するインスタンス + */ + public static delete(physics: CubismPhysics): void { + if (physics != null) { + physics.release(); + physics = null; + } + } + + /** + * physics3.jsonをパースする。 + * @param physicsJson physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public parse(physicsJson: ArrayBuffer, size: number): void { + this._physicsRig = new CubismPhysicsRig(); + + let json: CubismPhysicsJson = new CubismPhysicsJson(physicsJson, size); + + this._physicsRig.gravity = json.getGravity(); + this._physicsRig.wind = json.getWind(); + this._physicsRig.subRigCount = json.getSubRigCount(); + + this._physicsRig.fps = json.getFps(); + + updateSize( + this._physicsRig.settings, + this._physicsRig.subRigCount, + CubismPhysicsSubRig, + true + ); + updateSize( + this._physicsRig.inputs, + json.getTotalInputCount(), + CubismPhysicsInput, + true + ); + updateSize( + this._physicsRig.outputs, + json.getTotalOutputCount(), + CubismPhysicsOutput, + true + ); + updateSize( + this._physicsRig.particles, + json.getVertexCount(), + CubismPhysicsParticle, + true + ); + + this._currentRigOutputs.length = 0; + this._previousRigOutputs.length = 0; + + let inputIndex = 0, + outputIndex = 0, + particleIndex = 0; + + let dstIndexCurrentRigOutputs: number = this._currentRigOutputs.length; + let dstIndexPreviousRigOutputs: number = this._previousRigOutputs.length; + this._currentRigOutputs.length += this._physicsRig.settings.length; + this._previousRigOutputs.length += this._physicsRig.settings.length; + for (let i = 0; i < this._physicsRig.settings.length; ++i) { + this._physicsRig.settings[i].normalizationPosition.minimum = + json.getNormalizationPositionMinimumValue(i); + this._physicsRig.settings[i].normalizationPosition.maximum = + json.getNormalizationPositionMaximumValue(i); + this._physicsRig.settings[i].normalizationPosition.defalut = + json.getNormalizationPositionDefaultValue(i); + + this._physicsRig.settings[i].normalizationAngle.minimum = + json.getNormalizationAngleMinimumValue(i); + this._physicsRig.settings[i].normalizationAngle.maximum = + json.getNormalizationAngleMaximumValue(i); + this._physicsRig.settings[i].normalizationAngle.defalut = + json.getNormalizationAngleDefaultValue(i); + + // Input + this._physicsRig.settings[i].inputCount = json.getInputCount(i); + this._physicsRig.settings[i].baseInputIndex = inputIndex; + + for (let j = 0; j < this._physicsRig.settings[i].inputCount; ++j) { + this._physicsRig.inputs[inputIndex + j].sourceParameterIndex = -1; + this._physicsRig.inputs[inputIndex + j].weight = json.getInputWeight( + i, + j + ); + this._physicsRig.inputs[inputIndex + j].reflect = json.getInputReflect( + i, + j + ); + + if (json.getInputType(i, j) == PhysicsTypeTagX) { + this._physicsRig.inputs[inputIndex + j].type = + CubismPhysicsSource.CubismPhysicsSource_X; + this._physicsRig.inputs[inputIndex + j].getNormalizedParameterValue = + getInputTranslationXFromNormalizedParameterValue; + } else if (json.getInputType(i, j) == PhysicsTypeTagY) { + this._physicsRig.inputs[inputIndex + j].type = + CubismPhysicsSource.CubismPhysicsSource_Y; + this._physicsRig.inputs[inputIndex + j].getNormalizedParameterValue = + getInputTranslationYFromNormalizedParamterValue; + } else if (json.getInputType(i, j) == PhysicsTypeTagAngle) { + this._physicsRig.inputs[inputIndex + j].type = + CubismPhysicsSource.CubismPhysicsSource_Angle; + this._physicsRig.inputs[inputIndex + j].getNormalizedParameterValue = + getInputAngleFromNormalizedParameterValue; + } + + this._physicsRig.inputs[inputIndex + j].source.targetType = + CubismPhysicsTargetType.CubismPhysicsTargetType_Parameter; + this._physicsRig.inputs[inputIndex + j].source.id = + json.getInputSourceId(i, j); + } + inputIndex += this._physicsRig.settings[i].inputCount; + + // Output + this._physicsRig.settings[i].outputCount = json.getOutputCount(i); + this._physicsRig.settings[i].baseOutputIndex = outputIndex; + + const currentRigOutput = new PhysicsOutput(); + updateSize( + currentRigOutput.outputs, + this._physicsRig.settings[i].outputCount, + null, + true + ); + + const previousRigOutput = new PhysicsOutput(); + updateSize( + previousRigOutput.outputs, + this._physicsRig.settings[i].outputCount, + null, + true + ); + + for (let j = 0; j < this._physicsRig.settings[i].outputCount; ++j) { + // initialize + currentRigOutput.outputs[j] = 0.0; + previousRigOutput.outputs[j] = 0.0; + + this._physicsRig.outputs[outputIndex + j].destinationParameterIndex = + -1; + this._physicsRig.outputs[outputIndex + j].vertexIndex = + json.getOutputVertexIndex(i, j); + this._physicsRig.outputs[outputIndex + j].angleScale = + json.getOutputAngleScale(i, j); + this._physicsRig.outputs[outputIndex + j].weight = json.getOutputWeight( + i, + j + ); + this._physicsRig.outputs[outputIndex + j].destination.targetType = + CubismPhysicsTargetType.CubismPhysicsTargetType_Parameter; + + this._physicsRig.outputs[outputIndex + j].destination.id = + json.getOutputDestinationId(i, j); + + if (json.getOutputType(i, j) == PhysicsTypeTagX) { + this._physicsRig.outputs[outputIndex + j].type = + CubismPhysicsSource.CubismPhysicsSource_X; + this._physicsRig.outputs[outputIndex + j].getValue = + getOutputTranslationX; + this._physicsRig.outputs[outputIndex + j].getScale = + getOutputScaleTranslationX; + } else if (json.getOutputType(i, j) == PhysicsTypeTagY) { + this._physicsRig.outputs[outputIndex + j].type = + CubismPhysicsSource.CubismPhysicsSource_Y; + this._physicsRig.outputs[outputIndex + j].getValue = + getOutputTranslationY; + this._physicsRig.outputs[outputIndex + j].getScale = + getOutputScaleTranslationY; + } else if (json.getOutputType(i, j) == PhysicsTypeTagAngle) { + this._physicsRig.outputs[outputIndex + j].type = + CubismPhysicsSource.CubismPhysicsSource_Angle; + this._physicsRig.outputs[outputIndex + j].getValue = getOutputAngle; + this._physicsRig.outputs[outputIndex + j].getScale = + getOutputScaleAngle; + } + + this._physicsRig.outputs[outputIndex + j].reflect = + json.getOutputReflect(i, j); + } + + this._currentRigOutputs[dstIndexCurrentRigOutputs++] = currentRigOutput; + this._previousRigOutputs[dstIndexPreviousRigOutputs++] = + previousRigOutput; + + outputIndex += this._physicsRig.settings[i].outputCount; + + // Particle + this._physicsRig.settings[i].particleCount = json.getParticleCount(i); + this._physicsRig.settings[i].baseParticleIndex = particleIndex; + + for (let j = 0; j < this._physicsRig.settings[i].particleCount; ++j) { + this._physicsRig.particles[particleIndex + j].mobility = + json.getParticleMobility(i, j); + this._physicsRig.particles[particleIndex + j].delay = + json.getParticleDelay(i, j); + this._physicsRig.particles[particleIndex + j].acceleration = + json.getParticleAcceleration(i, j); + this._physicsRig.particles[particleIndex + j].radius = + json.getParticleRadius(i, j); + this._physicsRig.particles[particleIndex + j].position = + json.getParticlePosition(i, j); + } + + particleIndex += this._physicsRig.settings[i].particleCount; + } + + this.initialize(); + + json.release(); + json = void 0; + json = null; + } + + /** + * 現在のパラメータ値で物理演算が安定化する状態を演算する。 + * @param model 物理演算の結果を適用するモデル + */ + public stabilization(model: CubismModel): void { + let totalAngle: { angle: number }; + let weight: number; + let radAngle: number; + let outputValue: number; + const totalTranslation: CubismVector2 = new CubismVector2(); + let currentSetting: CubismPhysicsSubRig; + let currentInputs: CubismPhysicsInput[]; + let currentOutputs: CubismPhysicsOutput[]; + let currentParticles: CubismPhysicsParticle[]; + + const parameterValues: Float32Array = model.getModel().parameters.values; + const parameterMaximumValues: Float32Array = + model.getModel().parameters.maximumValues; + const parameterMinimumValues: Float32Array = + model.getModel().parameters.minimumValues; + const parameterDefaultValues: Float32Array = + model.getModel().parameters.defaultValues; + + if ((this._parameterCaches?.length ?? 0) < model.getParameterCount()) { + this._parameterCaches = new Float32Array(model.getParameterCount()); + } + + if ((this._parameterInputCaches?.length ?? 0) < model.getParameterCount()) { + this._parameterInputCaches = new Float32Array(model.getParameterCount()); + } + + for (let j = 0; j < model.getParameterCount(); ++j) { + this._parameterCaches[j] = parameterValues[j]; + this._parameterInputCaches[j] = parameterValues[j]; + } + + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + totalAngle = { angle: 0.0 }; + totalTranslation.x = 0.0; + totalTranslation.y = 0.0; + currentSetting = this._physicsRig.settings[settingIndex]; + currentInputs = this._physicsRig.inputs.slice( + currentSetting.baseInputIndex + ); + currentOutputs = this._physicsRig.outputs.slice( + currentSetting.baseOutputIndex + ); + currentParticles = this._physicsRig.particles.slice( + currentSetting.baseParticleIndex + ); + + // Load input parameters + for (let i = 0; i < currentSetting.inputCount; ++i) { + weight = currentInputs[i].weight / MaximumWeight; + + if (currentInputs[i].sourceParameterIndex == -1) { + currentInputs[i].sourceParameterIndex = model.getParameterIndex( + currentInputs[i].source.id + ); + } + + currentInputs[i].getNormalizedParameterValue( + totalTranslation, + totalAngle, + parameterValues[currentInputs[i].sourceParameterIndex], + parameterMinimumValues[currentInputs[i].sourceParameterIndex], + parameterMaximumValues[currentInputs[i].sourceParameterIndex], + parameterDefaultValues[currentInputs[i].sourceParameterIndex], + currentSetting.normalizationPosition, + currentSetting.normalizationAngle, + currentInputs[i].reflect, + weight + ); + + this._parameterCaches[currentInputs[i].sourceParameterIndex] = + parameterValues[currentInputs[i].sourceParameterIndex]; + } + + radAngle = CubismMath.degreesToRadian(-totalAngle.angle); + + totalTranslation.x = + totalTranslation.x * CubismMath.cos(radAngle) - + totalTranslation.y * CubismMath.sin(radAngle); + totalTranslation.y = + totalTranslation.x * CubismMath.sin(radAngle) + + totalTranslation.y * CubismMath.cos(radAngle); + + // Calculate particles position. + updateParticlesForStabilization( + currentParticles, + currentSetting.particleCount, + totalTranslation, + totalAngle.angle, + this._options.wind, + MovementThreshold * currentSetting.normalizationPosition.maximum + ); + + // Update output parameters. + for (let i = 0; i < currentSetting.outputCount; ++i) { + const particleIndex = currentOutputs[i].vertexIndex; + + if (currentOutputs[i].destinationParameterIndex == -1) { + currentOutputs[i].destinationParameterIndex = model.getParameterIndex( + currentOutputs[i].destination.id + ); + } + + if ( + particleIndex < 1 || + particleIndex >= currentSetting.particleCount + ) { + continue; + } + + let translation: CubismVector2 = new CubismVector2(); + translation = currentParticles[particleIndex].position.substract( + currentParticles[particleIndex - 1].position + ); + + outputValue = currentOutputs[i].getValue( + translation, + currentParticles, + particleIndex, + currentOutputs[i].reflect, + this._options.gravity + ); + + this._currentRigOutputs[settingIndex].outputs[i] = outputValue; + this._previousRigOutputs[settingIndex].outputs[i] = outputValue; + + const destinationParameterIndex: number = + currentOutputs[i].destinationParameterIndex; + + const outParameterCaches: Float32Array = + !Float32Array.prototype.slice && 'subarray' in Float32Array.prototype + ? JSON.parse( + JSON.stringify( + parameterValues.subarray(destinationParameterIndex) + ) + ) // 値渡しするため、JSON.parse, JSON.stringify + : parameterValues.slice(destinationParameterIndex); + + updateOutputParameterValue( + outParameterCaches, + parameterMinimumValues[destinationParameterIndex], + parameterMaximumValues[destinationParameterIndex], + outputValue, + currentOutputs[i] + ); + + // 値を反映 + for ( + let offset: number = destinationParameterIndex, outParamIndex = 0; + offset < this._parameterCaches.length; + offset++, outParamIndex++ + ) { + parameterValues[offset] = this._parameterCaches[offset] = + outParameterCaches[outParamIndex]; + } + } + } + } + + /** + * 物理演算の評価 + * + * Pendulum interpolation weights + * + * 振り子の計算結果は保存され、パラメータへの出力は保存された前回の結果で補間されます。 + * The result of the pendulum calculation is saved and + * the output to the parameters is interpolated with the saved previous result of the pendulum calculation. + * + * 図で示すと[1]と[2]で補間されます。 + * The figure shows the interpolation between [1] and [2]. + * + * 補間の重みは最新の振り子計算タイミングと次回のタイミングの間で見た現在時間で決定する。 + * The weight of the interpolation are determined by the current time seen between + * the latest pendulum calculation timing and the next timing. + * + * 図で示すと[2]と[4]の間でみた(3)の位置の重みになる。 + * Figure shows the weight of position (3) as seen between [2] and [4]. + * + * 解釈として振り子計算のタイミングと重み計算のタイミングがズレる。 + * As an interpretation, the pendulum calculation and weights are misaligned. + * + * physics3.jsonにFPS情報が存在しない場合は常に前の振り子状態で設定される。 + * If there is no FPS information in physics3.json, it is always set in the previous pendulum state. + * + * この仕様は補間範囲を逸脱したことが原因の震えたような見た目を回避を目的にしている。 + * The purpose of this specification is to avoid the quivering appearance caused by deviations from the interpolation range. + * + * ------------ time --------------> + * + * |+++++|------| <- weight + * ==[1]====#=====[2]---(3)----(4) + * ^ output contents + * + * 1:_previousRigOutputs + * 2:_currentRigOutputs + * 3:_currentRemainTime (now rendering) + * 4:next particles timing + * @param model 物理演算の結果を適用するモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public evaluate(model: CubismModel, deltaTimeSeconds: number): void { + let totalAngle: { angle: number }; + let weight: number; + let radAngle: number; + let outputValue: number; + const totalTranslation: CubismVector2 = new CubismVector2(); + let currentSetting: CubismPhysicsSubRig; + let currentInputs: CubismPhysicsInput[]; + let currentOutputs: CubismPhysicsOutput[]; + let currentParticles: CubismPhysicsParticle[]; + + if (0.0 >= deltaTimeSeconds) { + return; + } + + const parameterValues: Float32Array = model.getModel().parameters.values; + const parameterMaximumValues: Float32Array = + model.getModel().parameters.maximumValues; + const parameterMinimumValues: Float32Array = + model.getModel().parameters.minimumValues; + const parameterDefaultValues: Float32Array = + model.getModel().parameters.defaultValues; + + let physicsDeltaTime: number; + this._currentRemainTime += deltaTimeSeconds; + if (this._currentRemainTime > MaxDeltaTime) { + this._currentRemainTime = 0.0; + } + + if ((this._parameterCaches?.length ?? 0) < model.getParameterCount()) { + this._parameterCaches = new Float32Array(model.getParameterCount()); + } + + if ((this._parameterInputCaches?.length ?? 0) < model.getParameterCount()) { + this._parameterInputCaches = new Float32Array(model.getParameterCount()); + for (let j = 0; j < model.getParameterCount(); ++j) { + this._parameterInputCaches[j] = parameterValues[j]; + } + } + + if (this._physicsRig.fps > 0.0) { + physicsDeltaTime = 1.0 / this._physicsRig.fps; + } else { + physicsDeltaTime = deltaTimeSeconds; + } + + while (this._currentRemainTime >= physicsDeltaTime) { + // copyRigOutputs _currentRigOutputs to _previousRigOutputs + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + currentSetting = this._physicsRig.settings[settingIndex]; + currentOutputs = this._physicsRig.outputs.slice( + currentSetting.baseOutputIndex + ); + for (let i = 0; i < currentSetting.outputCount; ++i) { + this._previousRigOutputs[settingIndex].outputs[i] = + this._currentRigOutputs[settingIndex].outputs[i]; + } + } + + // 入力キャッシュとパラメータで線形補間してUpdateParticlesするタイミングでの入力を計算する。 + // Calculate the input at the timing to UpdateParticles by linear interpolation with the _parameterInputCache and parameterValue. + // _parameterCacheはグループ間での値の伝搬の役割があるので_parameterInputCacheとの分離が必要。 + // _parameterCache needs to be separated from _parameterInputCache because of its role in propagating values between groups. + const inputWeight = physicsDeltaTime / this._currentRemainTime; + for (let j = 0; j < model.getParameterCount(); ++j) { + this._parameterCaches[j] = + this._parameterInputCaches[j] * (1.0 - inputWeight) + + parameterValues[j] * inputWeight; + this._parameterInputCaches[j] = this._parameterCaches[j]; + } + + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + totalAngle = { angle: 0.0 }; + totalTranslation.x = 0.0; + totalTranslation.y = 0.0; + currentSetting = this._physicsRig.settings[settingIndex]; + currentInputs = this._physicsRig.inputs.slice( + currentSetting.baseInputIndex + ); + currentOutputs = this._physicsRig.outputs.slice( + currentSetting.baseOutputIndex + ); + currentParticles = this._physicsRig.particles.slice( + currentSetting.baseParticleIndex + ); + + // Load input parameters + for (let i = 0; i < currentSetting.inputCount; ++i) { + weight = currentInputs[i].weight / MaximumWeight; + + if (currentInputs[i].sourceParameterIndex == -1) { + currentInputs[i].sourceParameterIndex = model.getParameterIndex( + currentInputs[i].source.id + ); + } + + currentInputs[i].getNormalizedParameterValue( + totalTranslation, + totalAngle, + this._parameterCaches[currentInputs[i].sourceParameterIndex], + parameterMinimumValues[currentInputs[i].sourceParameterIndex], + parameterMaximumValues[currentInputs[i].sourceParameterIndex], + parameterDefaultValues[currentInputs[i].sourceParameterIndex], + currentSetting.normalizationPosition, + currentSetting.normalizationAngle, + currentInputs[i].reflect, + weight + ); + } + + radAngle = CubismMath.degreesToRadian(-totalAngle.angle); + + totalTranslation.x = + totalTranslation.x * CubismMath.cos(radAngle) - + totalTranslation.y * CubismMath.sin(radAngle); + totalTranslation.y = + totalTranslation.x * CubismMath.sin(radAngle) + + totalTranslation.y * CubismMath.cos(radAngle); + + // Calculate particles position. + updateParticles( + currentParticles, + currentSetting.particleCount, + totalTranslation, + totalAngle.angle, + this._options.wind, + MovementThreshold * currentSetting.normalizationPosition.maximum, + physicsDeltaTime, + AirResistance + ); + + // Update output parameters. + for (let i = 0; i < currentSetting.outputCount; ++i) { + const particleIndex = currentOutputs[i].vertexIndex; + + if (currentOutputs[i].destinationParameterIndex == -1) { + currentOutputs[i].destinationParameterIndex = + model.getParameterIndex(currentOutputs[i].destination.id); + } + + if ( + particleIndex < 1 || + particleIndex >= currentSetting.particleCount + ) { + continue; + } + + const translation: CubismVector2 = new CubismVector2(); + translation.x = + currentParticles[particleIndex].position.x - + currentParticles[particleIndex - 1].position.x; + translation.y = + currentParticles[particleIndex].position.y - + currentParticles[particleIndex - 1].position.y; + + outputValue = currentOutputs[i].getValue( + translation, + currentParticles, + particleIndex, + currentOutputs[i].reflect, + this._options.gravity + ); + + this._currentRigOutputs[settingIndex].outputs[i] = outputValue; + + const destinationParameterIndex: number = + currentOutputs[i].destinationParameterIndex; + const outParameterCaches: Float32Array = + !Float32Array.prototype.slice && + 'subarray' in Float32Array.prototype + ? JSON.parse( + JSON.stringify( + this._parameterCaches.subarray(destinationParameterIndex) + ) + ) // 値渡しするため、JSON.parse, JSON.stringify + : this._parameterCaches.slice(destinationParameterIndex); + + updateOutputParameterValue( + outParameterCaches, + parameterMinimumValues[destinationParameterIndex], + parameterMaximumValues[destinationParameterIndex], + outputValue, + currentOutputs[i] + ); + + // 値を反映 + for ( + let offset: number = destinationParameterIndex, outParamIndex = 0; + offset < this._parameterCaches.length; + offset++, outParamIndex++ + ) { + this._parameterCaches[offset] = outParameterCaches[outParamIndex]; + } + } + } + this._currentRemainTime -= physicsDeltaTime; + } + + const alpha: number = this._currentRemainTime / physicsDeltaTime; + this.interpolate(model, alpha); + } + + /** + * 物理演算結果の適用 + * 振り子演算の最新の結果と一つ前の結果から指定した重みで適用する。 + * @param model 物理演算の結果を適用するモデル + * @param weight 最新結果の重み + */ + public interpolate(model: CubismModel, weight: number): void { + let currentOutputs: CubismPhysicsOutput[]; + let currentSetting: CubismPhysicsSubRig; + const parameterValues: Float32Array = model.getModel().parameters.values; + const parameterMaximumValues: Float32Array = + model.getModel().parameters.maximumValues; + const parameterMinimumValues: Float32Array = + model.getModel().parameters.minimumValues; + + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + currentSetting = this._physicsRig.settings[settingIndex]; + currentOutputs = this._physicsRig.outputs.slice( + currentSetting.baseOutputIndex + ); + + // Load input parameters. + for (let i = 0; i < currentSetting.outputCount; ++i) { + if (currentOutputs[i].destinationParameterIndex == -1) { + continue; + } + + const destinationParameterIndex: number = + currentOutputs[i].destinationParameterIndex; + const outParameterValues: Float32Array = + !Float32Array.prototype.slice && 'subarray' in Float32Array.prototype + ? JSON.parse( + JSON.stringify( + parameterValues.subarray(destinationParameterIndex) + ) + ) // 値渡しするため、JSON.parse, JSON.stringify + : parameterValues.slice(destinationParameterIndex); + + updateOutputParameterValue( + outParameterValues, + parameterMinimumValues[destinationParameterIndex], + parameterMaximumValues[destinationParameterIndex], + this._previousRigOutputs[settingIndex].outputs[i] * (1 - weight) + + this._currentRigOutputs[settingIndex].outputs[i] * weight, + currentOutputs[i] + ); + + // 値を反映 + for ( + let offset: number = destinationParameterIndex, outParamIndex = 0; + offset < parameterValues.length; + offset++, outParamIndex++ + ) { + parameterValues[offset] = outParameterValues[outParamIndex]; + } + } + } + } + + /** + * オプションの設定 + * @param options オプション + */ + public setOptions(options: Options): void { + this._options = options; + } + + /** + * オプションの取得 + * @return オプション + */ + public getOption(): Options { + return this._options; + } + + /** + * コンストラクタ + */ + public constructor() { + this._physicsRig = null; + + // set default options + this._options = new Options(); + this._options.gravity.y = -1.0; + this._options.gravity.x = 0.0; + this._options.wind.x = 0.0; + this._options.wind.y = 0.0; + this._currentRigOutputs = new Array(); + this._previousRigOutputs = new Array(); + this._currentRemainTime = 0.0; + this._parameterCaches = null; + this._parameterInputCaches = null; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._physicsRig = void 0; + this._physicsRig = null; + } + + /** + * 初期化する + */ + public initialize(): void { + let strand: CubismPhysicsParticle[]; + let currentSetting: CubismPhysicsSubRig; + let radius: CubismVector2; + + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + currentSetting = this._physicsRig.settings[settingIndex]; + strand = this._physicsRig.particles.slice( + currentSetting.baseParticleIndex + ); + + // Initialize the top of particle. + strand[0].initialPosition = new CubismVector2(0.0, 0.0); + strand[0].lastPosition = new CubismVector2( + strand[0].initialPosition.x, + strand[0].initialPosition.y + ); + strand[0].lastGravity = new CubismVector2(0.0, -1.0); + strand[0].lastGravity.y *= -1.0; + strand[0].velocity = new CubismVector2(0.0, 0.0); + strand[0].force = new CubismVector2(0.0, 0.0); + + // Initialize particles. + for (let i = 1; i < currentSetting.particleCount; ++i) { + radius = new CubismVector2(0.0, 0.0); + radius.y = strand[i].radius; + strand[i].initialPosition = new CubismVector2( + strand[i - 1].initialPosition.x + radius.x, + strand[i - 1].initialPosition.y + radius.y + ); + strand[i].position = new CubismVector2( + strand[i].initialPosition.x, + strand[i].initialPosition.y + ); + strand[i].lastPosition = new CubismVector2( + strand[i].initialPosition.x, + strand[i].initialPosition.y + ); + strand[i].lastGravity = new CubismVector2(0.0, -1.0); + strand[i].lastGravity.y *= -1.0; + strand[i].velocity = new CubismVector2(0.0, 0.0); + strand[i].force = new CubismVector2(0.0, 0.0); + } + } + } + + _physicsRig: CubismPhysicsRig; // 物理演算のデータ + _options: Options; // オプション + + _currentRigOutputs: Array; ///< 最新の振り子計算の結果 + _previousRigOutputs: Array; ///< 一つ前の振り子計算の結果 + + _currentRemainTime: number; ///< 物理演算が処理していない時間 + + _parameterCaches: Float32Array; ///< Evaluateで利用するパラメータのキャッシュ + _parameterInputCaches: Float32Array; ///< UpdateParticlesが動くときの入力をキャッシュ +} + +/** + * 物理演算のオプション + */ +export class Options { + constructor() { + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + } + + gravity: CubismVector2; // 重力方向 + wind: CubismVector2; // 風の方向 +} + +/** + * パラメータに適用する前の物理演算の出力結果 + */ +export class PhysicsOutput { + constructor() { + this.outputs = new Array(0); + } + + outputs: Array; // 物理演算出力結果 +} + +/** + * Gets sign. + * + * @param value Evaluation target value. + * + * @return Sign of value. + */ +function sign(value: number): number { + let ret = 0; + + if (value > 0.0) { + ret = 1; + } else if (value < 0.0) { + ret = -1; + } + + return ret; +} + +function getInputTranslationXFromNormalizedParameterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number +): void { + targetTranslation.x += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationPosition.minimum, + normalizationPosition.maximum, + normalizationPosition.defalut, + isInverted + ) * weight; +} + +function getInputTranslationYFromNormalizedParamterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number +): void { + targetTranslation.y += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationPosition.minimum, + normalizationPosition.maximum, + normalizationPosition.defalut, + isInverted + ) * weight; +} + +function getInputAngleFromNormalizedParameterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizaitionPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number +): void { + targetAngle.angle += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationAngle.minimum, + normalizationAngle.maximum, + normalizationAngle.defalut, + isInverted + ) * weight; +} + +function getOutputTranslationX( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 +): number { + let outputValue: number = translation.x; + + if (isInverted) { + outputValue *= -1.0; + } + + return outputValue; +} + +function getOutputTranslationY( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 +): number { + let outputValue: number = translation.y; + + if (isInverted) { + outputValue *= -1.0; + } + return outputValue; +} + +function getOutputAngle( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 +): number { + let outputValue: number; + + if (particleIndex >= 2) { + parentGravity = particles[particleIndex - 1].position.substract( + particles[particleIndex - 2].position + ); + } else { + parentGravity = parentGravity.multiplyByScaler(-1.0); + } + + outputValue = CubismMath.directionToRadian(parentGravity, translation); + + if (isInverted) { + outputValue *= -1.0; + } + + return outputValue; +} + +function getRangeValue(min: number, max: number): number { + const maxValue: number = CubismMath.max(min, max); + const minValue: number = CubismMath.min(min, max); + + return CubismMath.abs(maxValue - minValue); +} + +function getDefaultValue(min: number, max: number): number { + const minValue: number = CubismMath.min(min, max); + return minValue + getRangeValue(min, max) / 2.0; +} + +function getOutputScaleTranslationX( + translationScale: CubismVector2, + angleScale: number +): number { + return JSON.parse(JSON.stringify(translationScale.x)); +} + +function getOutputScaleTranslationY( + translationScale: CubismVector2, + angleScale: number +): number { + return JSON.parse(JSON.stringify(translationScale.y)); +} + +function getOutputScaleAngle( + translationScale: CubismVector2, + angleScale: number +): number { + return JSON.parse(JSON.stringify(angleScale)); +} + +/** + * Updates particles. + * + * @param strand Target array of particle. + * @param strandCount Count of particle. + * @param totalTranslation Total translation value. + * @param totalAngle Total angle. + * @param windDirection Direction of Wind. + * @param thresholdValue Threshold of movement. + * @param deltaTimeSeconds Delta time. + * @param airResistance Air resistance. + */ +function updateParticles( + strand: CubismPhysicsParticle[], + strandCount: number, + totalTranslation: CubismVector2, + totalAngle: number, + windDirection: CubismVector2, + thresholdValue: number, + deltaTimeSeconds: number, + airResistance: number +) { + let delay: number; + let radian: number; + let direction: CubismVector2 = new CubismVector2(0.0, 0.0); + let velocity: CubismVector2 = new CubismVector2(0.0, 0.0); + let force: CubismVector2 = new CubismVector2(0.0, 0.0); + let newDirection: CubismVector2 = new CubismVector2(0.0, 0.0); + + strand[0].position = new CubismVector2( + totalTranslation.x, + totalTranslation.y + ); + + const totalRadian: number = CubismMath.degreesToRadian(totalAngle); + const currentGravity: CubismVector2 = + CubismMath.radianToDirection(totalRadian); + currentGravity.normalize(); + + for (let i = 1; i < strandCount; ++i) { + strand[i].force = currentGravity + .multiplyByScaler(strand[i].acceleration) + .add(windDirection); + + strand[i].lastPosition = new CubismVector2( + strand[i].position.x, + strand[i].position.y + ); + + delay = strand[i].delay * deltaTimeSeconds * 30.0; + + direction = strand[i].position.substract(strand[i - 1].position); + + radian = + CubismMath.directionToRadian(strand[i].lastGravity, currentGravity) / + airResistance; + + direction.x = + CubismMath.cos(radian) * direction.x - + direction.y * CubismMath.sin(radian); + direction.y = + CubismMath.sin(radian) * direction.x + + direction.y * CubismMath.cos(radian); + + strand[i].position = strand[i - 1].position.add(direction); + + velocity = strand[i].velocity.multiplyByScaler(delay); + force = strand[i].force.multiplyByScaler(delay).multiplyByScaler(delay); + + strand[i].position = strand[i].position.add(velocity).add(force); + + newDirection = strand[i].position.substract(strand[i - 1].position); + newDirection.normalize(); + + strand[i].position = strand[i - 1].position.add( + newDirection.multiplyByScaler(strand[i].radius) + ); + + if (CubismMath.abs(strand[i].position.x) < thresholdValue) { + strand[i].position.x = 0.0; + } + + if (delay != 0.0) { + strand[i].velocity = strand[i].position.substract(strand[i].lastPosition); + strand[i].velocity = strand[i].velocity.divisionByScalar(delay); + strand[i].velocity = strand[i].velocity.multiplyByScaler( + strand[i].mobility + ); + } + + strand[i].force = new CubismVector2(0.0, 0.0); + strand[i].lastGravity = new CubismVector2( + currentGravity.x, + currentGravity.y + ); + } +} + +/** + * Updates particles for stabilization. + * + * @param strand Target array of particle. + * @param strandCount Count of particle. + * @param totalTranslation Total translation value. + * @param totalAngle Total angle. + * @param windDirection Direction of Wind. + * @param thresholdValue Threshold of movement. + */ +function updateParticlesForStabilization( + strand: CubismPhysicsParticle[], + strandCount: number, + totalTranslation: CubismVector2, + totalAngle: number, + windDirection: CubismVector2, + thresholdValue: number +) { + let force: CubismVector2 = new CubismVector2(0.0, 0.0); + + strand[0].position = new CubismVector2( + totalTranslation.x, + totalTranslation.y + ); + + const totalRadian: number = CubismMath.degreesToRadian(totalAngle); + const currentGravity: CubismVector2 = + CubismMath.radianToDirection(totalRadian); + currentGravity.normalize(); + + for (let i = 1; i < strandCount; ++i) { + strand[i].force = currentGravity + .multiplyByScaler(strand[i].acceleration) + .add(windDirection); + + strand[i].lastPosition = new CubismVector2( + strand[i].position.x, + strand[i].position.y + ); + + strand[i].velocity = new CubismVector2(0.0, 0.0); + force = strand[i].force; + force.normalize(); + + force = force.multiplyByScaler(strand[i].radius); + strand[i].position = strand[i - 1].position.add(force); + + if (CubismMath.abs(strand[i].position.x) < thresholdValue) { + strand[i].position.x = 0.0; + } + + strand[i].force = new CubismVector2(0.0, 0.0); + strand[i].lastGravity = new CubismVector2( + currentGravity.x, + currentGravity.y + ); + } +} + +/** + * Updates output parameter value. + * @param parameterValue Target parameter value. + * @param parameterValueMinimum Minimum of parameter value. + * @param parameterValueMaximum Maximum of parameter value. + * @param translation Translation value. + */ +function updateOutputParameterValue( + parameterValue: Float32Array, + parameterValueMinimum: number, + parameterValueMaximum: number, + translation: number, + output: CubismPhysicsOutput +): void { + let value: number; + const outputScale: number = output.getScale( + output.translationScale, + output.angleScale + ); + + value = translation * outputScale; + + if (value < parameterValueMinimum) { + if (value < output.valueBelowMinimum) { + output.valueBelowMinimum = value; + } + + value = parameterValueMinimum; + } else if (value > parameterValueMaximum) { + if (value > output.valueExceededMaximum) { + output.valueExceededMaximum = value; + } + + value = parameterValueMaximum; + } + + const weight: number = output.weight / MaximumWeight; + + if (weight >= 1.0) { + parameterValue[0] = value; + } else { + value = parameterValue[0] * (1.0 - weight) + value * weight; + parameterValue[0] = value; + } +} + +function normalizeParameterValue( + value: number, + parameterMinimum: number, + parameterMaximum: number, + parameterDefault: number, + normalizedMinimum: number, + normalizedMaximum: number, + normalizedDefault: number, + isInverted: boolean +) { + let result = 0.0; + + const maxValue: number = CubismMath.max(parameterMaximum, parameterMinimum); + + if (maxValue < value) { + value = maxValue; + } + + const minValue: number = CubismMath.min(parameterMaximum, parameterMinimum); + + if (minValue > value) { + value = minValue; + } + + const minNormValue: number = CubismMath.min( + normalizedMinimum, + normalizedMaximum + ); + const maxNormValue: number = CubismMath.max( + normalizedMinimum, + normalizedMaximum + ); + const middleNormValue: number = normalizedDefault; + + const middleValue: number = getDefaultValue(minValue, maxValue); + const paramValue: number = value - middleValue; + + switch (sign(paramValue)) { + case 1: { + const nLength: number = maxNormValue - middleNormValue; + const pLength: number = maxValue - middleValue; + + if (pLength != 0.0) { + result = paramValue * (nLength / pLength); + result += middleNormValue; + } + + break; + } + case -1: { + const nLength: number = minNormValue - middleNormValue; + const pLength: number = minValue - middleValue; + + if (pLength != 0.0) { + result = paramValue * (nLength / pLength); + result += middleNormValue; + } + + break; + } + case 0: { + result = middleNormValue; + + break; + } + default: { + break; + } + } + + return isInverted ? result : result * -1.0; +} + +// Namespace definition for compatibility. +import * as $ from './cubismphysics'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPhysics = $.CubismPhysics; + export type CubismPhysics = $.CubismPhysics; + export const Options = $.Options; + export type Options = $.Options; +} diff --git a/avatar-h5-renderer/framework/src/physics/cubismphysicsinternal.ts b/avatar-h5-renderer/framework/src/physics/cubismphysicsinternal.ts new file mode 100644 index 0000000..276f61d --- /dev/null +++ b/avatar-h5-renderer/framework/src/physics/cubismphysicsinternal.ts @@ -0,0 +1,251 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismVector2 } from '../math/cubismvector2'; + +/** + * 物理演算の適用先の種類 + */ +export enum CubismPhysicsTargetType { + CubismPhysicsTargetType_Parameter // パラメータに対して適用 +} + +/** + * 物理演算の入力の種類 + */ +export enum CubismPhysicsSource { + CubismPhysicsSource_X, // X軸の位置から + CubismPhysicsSource_Y, // Y軸の位置から + CubismPhysicsSource_Angle // 角度から +} + +/** + * @brief 物理演算で使用する外部の力 + * + * 物理演算で使用する外部の力。 + */ +export class PhysicsJsonEffectiveForces { + constructor() { + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + } + gravity: CubismVector2; // 重力 + wind: CubismVector2; // 風 +} + +/** + * 物理演算のパラメータ情報 + */ +export class CubismPhysicsParameter { + id: CubismIdHandle; // パラメータ + targetType: CubismPhysicsTargetType; // 適用先の種類 +} + +/** + * 物理演算の正規化情報 + */ +export class CubismPhysicsNormalization { + minimum: number; // 最大値 + maximum: number; // 最小値 + defalut: number; // デフォルト値 +} + +/** + * 物理演算の演算委使用する物理点の情報 + */ +export class CubismPhysicsParticle { + constructor() { + this.initialPosition = new CubismVector2(0, 0); + this.position = new CubismVector2(0, 0); + this.lastPosition = new CubismVector2(0, 0); + this.lastGravity = new CubismVector2(0, 0); + this.force = new CubismVector2(0, 0); + this.velocity = new CubismVector2(0, 0); + } + + initialPosition: CubismVector2; // 初期位置 + mobility: number; // 動きやすさ + delay: number; // 遅れ + acceleration: number; // 加速度 + radius: number; // 距離 + position: CubismVector2; // 現在の位置 + lastPosition: CubismVector2; // 最後の位置 + lastGravity: CubismVector2; // 最後の重力 + force: CubismVector2; // 現在かかっている力 + velocity: CubismVector2; // 現在の速度 +} + +/** + * 物理演算の物理点の管理 + */ +export class CubismPhysicsSubRig { + constructor() { + this.normalizationPosition = new CubismPhysicsNormalization(); + this.normalizationAngle = new CubismPhysicsNormalization(); + } + inputCount: number; // 入力の個数 + outputCount: number; // 出力の個数 + particleCount: number; // 物理点の個数 + baseInputIndex: number; // 入力の最初のインデックス + baseOutputIndex: number; // 出力の最初のインデックス + baseParticleIndex: number; // 物理点の最初のインデックス + normalizationPosition: CubismPhysicsNormalization; // 正規化された位置 + normalizationAngle: CubismPhysicsNormalization; // 正規化された角度 +} + +/** + * 正規化されたパラメータの取得関数の宣言 + * @param targetTranslation // 演算結果の移動値 + * @param targetAngle // 演算結果の角度 + * @param value // パラメータの値 + * @param parameterMinimunValue // パラメータの最小値 + * @param parameterMaximumValue // パラメータの最大値 + * @param parameterDefaultValue // パラメータのデフォルト値 + * @param normalizationPosition // 正規化された位置 + * @param normalizationAngle // 正規化された角度 + * @param isInverted // 値が反転されているか? + * @param weight // 重み + */ +export interface normalizedPhysicsParameterValueGetter { + ( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimunValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number + ): void; +} + +/** + * 物理演算の値の取得関数の宣言 + * @param translation 移動値 + * @param particles 物理点のリスト + * @param isInverted 値が反映されているか + * @param parentGravity 重力 + * @return 値 + */ +export interface physicsValueGetter { + ( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 + ): number; +} + +/** + * 物理演算のスケールの取得関数の宣言 + * @param translationScale 移動値のスケール + * @param angleScale 角度のスケール + * @return スケール値 + */ +export interface physicsScaleGetter { + (translationScale: CubismVector2, angleScale: number): number; +} + +/** + * 物理演算の入力情報 + */ +export class CubismPhysicsInput { + constructor() { + this.source = new CubismPhysicsParameter(); + } + source: CubismPhysicsParameter; // 入力元のパラメータ + sourceParameterIndex: number; // 入力元のパラメータのインデックス + weight: number; // 重み + type: number; // 入力の種類 + reflect: boolean; // 値が反転されているかどうか + getNormalizedParameterValue: normalizedPhysicsParameterValueGetter; // 正規化されたパラメータ値の取得関数 +} + +/** + * @brief 物理演算の出力情報 + * + * 物理演算の出力情報。 + */ +export class CubismPhysicsOutput { + constructor() { + this.destination = new CubismPhysicsParameter(); + this.translationScale = new CubismVector2(0, 0); + } + + destination: CubismPhysicsParameter; // 出力先のパラメータ + destinationParameterIndex: number; // 出力先のパラメータのインデックス + vertexIndex: number; // 振り子のインデックス + translationScale: CubismVector2; // 移動値のスケール + angleScale: number; // 角度のスケール + weight: number; // 重み + type: CubismPhysicsSource; // 出力の種類 + reflect: boolean; // 値が反転されているかどうか + valueBelowMinimum: number; // 最小値を下回った時の値 + valueExceededMaximum: number; // 最大値をこえた時の値 + getValue: physicsValueGetter; // 物理演算の値の取得関数 + getScale: physicsScaleGetter; // 物理演算のスケール値の取得関数 +} + +/** + * @brief 物理演算のデータ + * + * 物理演算のデータ。 + */ +export class CubismPhysicsRig { + constructor() { + this.settings = new Array(); + this.inputs = new Array(); + this.outputs = new Array(); + this.particles = new Array(); + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + this.fps = 0.0; + } + + subRigCount: number; // 物理演算の物理点の個数 + settings: Array; // 物理演算の物理点の管理のリスト + inputs: Array; // 物理演算の入力のリスト + outputs: Array; // 物理演算の出力のリスト + particles: Array; // 物理演算の物理点のリスト + gravity: CubismVector2; // 重力 + wind: CubismVector2; // 風 + fps: number; //物理演算動作FPS +} + +// Namespace definition for compatibility. +import * as $ from './cubismphysicsinternal'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPhysicsInput = $.CubismPhysicsInput; + export type CubismPhysicsInput = $.CubismPhysicsInput; + export const CubismPhysicsNormalization = $.CubismPhysicsNormalization; + export type CubismPhysicsNormalization = $.CubismPhysicsNormalization; + export const CubismPhysicsOutput = $.CubismPhysicsOutput; + export type CubismPhysicsOutput = $.CubismPhysicsOutput; + export const CubismPhysicsParameter = $.CubismPhysicsParameter; + export type CubismPhysicsParameter = $.CubismPhysicsParameter; + export const CubismPhysicsParticle = $.CubismPhysicsParticle; + export type CubismPhysicsParticle = $.CubismPhysicsParticle; + export const CubismPhysicsRig = $.CubismPhysicsRig; + export type CubismPhysicsRig = $.CubismPhysicsRig; + export const CubismPhysicsSource = $.CubismPhysicsSource; + export type CubismPhysicsSource = $.CubismPhysicsSource; + export const CubismPhysicsSubRig = $.CubismPhysicsSubRig; + export type CubismPhysicsSubRig = $.CubismPhysicsSubRig; + export const CubismPhysicsTargetType = $.CubismPhysicsTargetType; + export type CubismPhysicsTargetType = $.CubismPhysicsTargetType; + export const PhysicsJsonEffectiveForces = $.PhysicsJsonEffectiveForces; + export type PhysicsJsonEffectiveForces = $.PhysicsJsonEffectiveForces; + export type normalizedPhysicsParameterValueGetter = + $.normalizedPhysicsParameterValueGetter; + export type physicsScaleGetter = $.physicsScaleGetter; + export type physicsValueGetter = $.physicsValueGetter; +} diff --git a/avatar-h5-renderer/framework/src/physics/cubismphysicsjson.ts b/avatar-h5-renderer/framework/src/physics/cubismphysicsjson.ts new file mode 100644 index 0000000..308b10f --- /dev/null +++ b/avatar-h5-renderer/framework/src/physics/cubismphysicsjson.ts @@ -0,0 +1,658 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismVector2 } from '../math/cubismvector2'; +import { CubismJson } from '../utils/cubismjson'; + +// JSON keys +const Position = 'Position'; +const X = 'X'; +const Y = 'Y'; +const Angle = 'Angle'; +const Type = 'Type'; +const Id = 'Id'; + +// Meta +const Meta = 'Meta'; +const EffectiveForces = 'EffectiveForces'; +const TotalInputCount = 'TotalInputCount'; +const TotalOutputCount = 'TotalOutputCount'; +const PhysicsSettingCount = 'PhysicsSettingCount'; +const Gravity = 'Gravity'; +const Wind = 'Wind'; +const VertexCount = 'VertexCount'; +const Fps = 'Fps'; + +// PhysicsSettings +const PhysicsSettings = 'PhysicsSettings'; +const Normalization = 'Normalization'; +const Minimum = 'Minimum'; +const Maximum = 'Maximum'; +const Default = 'Default'; +const Reflect = 'Reflect'; +const Weight = 'Weight'; + +// Input +const Input = 'Input'; +const Source = 'Source'; + +// Output +const Output = 'Output'; +const Scale = 'Scale'; +const VertexIndex = 'VertexIndex'; +const Destination = 'Destination'; + +// Particle +const Vertices = 'Vertices'; +const Mobility = 'Mobility'; +const Delay = 'Delay'; +const Radius = 'Radius'; +const Acceleration = 'Acceleration'; + +/** + * physics3.jsonのコンテナ。 + */ +export class CubismPhysicsJson { + /** + * コンストラクタ + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * 重力の取得 + * @return 重力 + */ + public getGravity(): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Gravity) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Gravity) + .getValueByString(Y) + .toFloat(); + return ret; + } + + /** + * 風の取得 + * @return 風 + */ + public getWind(): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Wind) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Wind) + .getValueByString(Y) + .toFloat(); + return ret; + } + + /** + * 物理演算設定FPSの取得 + * @return 物理演算設定FPS + */ + public getFps(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Fps) + .toFloat(0.0); + } + + /** + * 物理店の管理の個数の取得 + * @return 物理店の管理の個数 + */ + public getSubRigCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(PhysicsSettingCount) + .toInt(); + } + + /** + * 入力の総合計の取得 + * @return 入力の総合計 + */ + public getTotalInputCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalInputCount) + .toInt(); + } + + /** + * 出力の総合計の取得 + * @return 出力の総合計 + */ + public getTotalOutputCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalOutputCount) + .toInt(); + } + + /** + * 物理点の個数の取得 + * @return 物理点の個数 + */ + public getVertexCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(VertexCount) + .toInt(); + } + + /** + * 正規化された位置の最小値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置の最小値 + */ + public getNormalizationPositionMinimumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Minimum) + .toFloat(); + } + + /** + * 正規化された位置の最大値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置の最大値 + */ + public getNormalizationPositionMaximumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Maximum) + .toFloat(); + } + + /** + * 正規化された位置のデフォルト値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置のデフォルト値 + */ + public getNormalizationPositionDefaultValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Default) + .toFloat(); + } + + /** + * 正規化された角度の最小値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された角度の最小値 + */ + public getNormalizationAngleMinimumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Minimum) + .toFloat(); + } + + /** + * 正規化された角度の最大値の取得 + * @param physicsSettingIndex + * @return 正規化された角度の最大値 + */ + public getNormalizationAngleMaximumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Maximum) + .toFloat(); + } + + /** + * 正規化された角度のデフォルト値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された角度のデフォルト値 + */ + public getNormalizationAngleDefaultValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Default) + .toFloat(); + } + + /** + * 入力の個数の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 入力の個数 + */ + public getInputCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getVector().length; + } + + /** + * 入力の重みの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の重み + */ + public getInputWeight( + physicsSettingIndex: number, + inputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Weight) + .toFloat(); + } + + /** + * 入力の反転の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の反転 + */ + public getInputReflect( + physicsSettingIndex: number, + inputIndex: number + ): boolean { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Reflect) + .toBoolean(); + } + + /** + * 入力の種類の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の種類 + */ + public getInputType(physicsSettingIndex: number, inputIndex: number): string { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Type) + .getRawString(); + } + + /** + * 入力元のIDの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力元のID + */ + public getInputSourceId( + physicsSettingIndex: number, + inputIndex: number + ): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Source) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * 出力の個数の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 出力の個数 + */ + public getOutputCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getVector().length; + } + + /** + * 出力の物理点のインデックスの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の物理点のインデックス + */ + public getOutputVertexIndex( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(VertexIndex) + .toInt(); + } + + /** + * 出力の角度のスケールを取得する + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の角度のスケール + */ + public getOutputAngleScale( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Scale) + .toFloat(); + } + + /** + * 出力の重みの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の重み + */ + public getOutputWeight( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Weight) + .toFloat(); + } + + /** + * 出力先のIDの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力先のID + */ + public getOutputDestinationId( + physicsSettingIndex: number, + outputIndex: number + ): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Destination) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * 出力の種類の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の種類 + */ + public getOutputType( + physicsSettingIndex: number, + outputIndex: number + ): string { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Type) + .getRawString(); + } + + /** + * 出力の反転の取得 + * @param physicsSettingIndex 物理演算のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の反転 + */ + public getOutputReflect( + physicsSettingIndex: number, + outputIndex: number + ): boolean { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Reflect) + .toBoolean(); + } + + /** + * 物理点の個数の取得 + * @param physicsSettingIndex 物理演算男設定のインデックス + * @return 物理点の個数 + */ + public getParticleCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getVector().length; + } + + /** + * 物理点の動きやすさの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の動きやすさ + */ + public getParticleMobility( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Mobility) + .toFloat(); + } + + /** + * 物理点の遅れの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の遅れ + */ + public getParticleDelay( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Delay) + .toFloat(); + } + + /** + * 物理点の加速度の取得 + * @param physicsSettingIndex 物理演算の設定 + * @param vertexIndex 物理点のインデックス + * @return 物理点の加速度 + */ + public getParticleAcceleration( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Acceleration) + .toFloat(); + } + + /** + * 物理点の距離の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の距離 + */ + public getParticleRadius( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Radius) + .toFloat(); + } + + /** + * 物理点の位置の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexInde 物理点のインデックス + * @return 物理点の位置 + */ + public getParticlePosition( + physicsSettingIndex: number, + vertexIndex: number + ): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Position) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Position) + .getValueByString(Y) + .toFloat(); + return ret; + } + + _json: CubismJson; // physics3.jsonデータ +} + +// Namespace definition for compatibility. +import * as $ from './cubismphysicsjson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPhysicsJson = $.CubismPhysicsJson; + export type CubismPhysicsJson = $.CubismPhysicsJson; +} diff --git a/avatar-h5-renderer/framework/src/rendering/cubismclippingmanager.ts b/avatar-h5-renderer/framework/src/rendering/cubismclippingmanager.ts new file mode 100644 index 0000000..b3f6f7f --- /dev/null +++ b/avatar-h5-renderer/framework/src/rendering/cubismclippingmanager.ts @@ -0,0 +1,994 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Constant } from '../live2dcubismframework'; +import { csmRect } from '../type/csmrectf'; +import { CubismMatrix44 } from '../math/cubismmatrix44'; +import { CubismModel } from '../model/cubismmodel'; +import { CubismClippingContext, CubismTextureColor } from './cubismrenderer'; +import { CubismLogError, CubismLogWarning } from '../utils/cubismdebug'; + +const ColorChannelCount = 4; // 実験時に1チャンネルの場合は1、RGBだけの場合は3、アルファも含める場合は4 +const ClippingMaskMaxCountOnDefault = 36; // 通常のフレームバッファ一枚あたりのマスク最大数 +const ClippingMaskMaxCountOnMultiRenderTexture = 32; // フレームバッファが2枚以上ある場合のフレームバッファ一枚あたりのマスク最大数 + +export type ClippingContextConstructor< + T_ClippingContext extends CubismClippingContext +> = new ( + manager: CubismClippingManager, + drawableMasks: Int32Array, + drawableMaskCounts: number +) => T_ClippingContext; + +export interface ICubismClippingManager { + getClippingMaskBufferSize(): number; +} + +export abstract class CubismClippingManager< + T_ClippingContext extends CubismClippingContext +> implements ICubismClippingManager { + /** + * コンストラクタ + */ + public constructor( + clippingContextFactory: ClippingContextConstructor + ) { + this._renderTextureCount = 0; + this._clippingMaskBufferSize = 256; + this._clippingContextListForMask = new Array(); + this._clippingContextListForDraw = new Array(); + this._clippingContextListForOffscreen = new Array(); + this._tmpBoundsOnModel = new csmRect(); + this._tmpMatrix = new CubismMatrix44(); + this._tmpMatrixForMask = new CubismMatrix44(); + this._tmpMatrixForDraw = new CubismMatrix44(); + this._clearedMaskBufferFlags = new Array(); + + this._clippingContexttConstructor = clippingContextFactory; + + this._channelColors = [ + new CubismTextureColor(1.0, 0.0, 0.0, 0.0), + new CubismTextureColor(0.0, 1.0, 0.0, 0.0), + new CubismTextureColor(0.0, 0.0, 1.0, 0.0), + new CubismTextureColor(0.0, 0.0, 0.0, 1.0) + ]; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for (let i = 0; i < this._clippingContextListForMask.length; i++) { + if (this._clippingContextListForMask[i]) { + this._clippingContextListForMask[i].release(); + this._clippingContextListForMask[i] = void 0; + } + this._clippingContextListForMask[i] = null; + } + this._clippingContextListForMask = null; + + // _clippingContextListForDrawは_clippingContextListForMaskにあるインスタンスを指している。上記の処理により要素ごとのDELETEは不要。 + for (let i = 0; i < this._clippingContextListForDraw.length; i++) { + this._clippingContextListForDraw[i] = null; + } + this._clippingContextListForDraw = null; + + for (let i = 0; i < this._channelColors.length; i++) { + this._channelColors[i] = null; + } + + this._channelColors = null; + + if (this._clearedMaskBufferFlags != null) { + this._clearedMaskBufferFlags.length = 0; + } + this._clearedMaskBufferFlags = null; + } + + /** + * マネージャの初期化処理 + * クリッピングマスクを使う描画オブジェクトの登録を行う + * @param model モデルのインスタンス + * @param renderTextureCount バッファの生成数 + */ + public initializeForDrawable( + model: CubismModel, + renderTextureCount: number + ): void { + // レンダーテクスチャの合計枚数の設定 + // 1以上の整数でない場合はそれぞれ警告を出す + if (renderTextureCount % 1 != 0) { + CubismLogWarning( + 'The number of render textures must be specified as an integer. The decimal point is rounded down and corrected to an integer.' + ); + // 小数点以下を除去 + renderTextureCount = ~~renderTextureCount; + } + if (renderTextureCount < 1) { + CubismLogWarning( + 'The number of render textures must be an integer greater than or equal to 1. Set the number of render textures to 1.' + ); + } + // 負の値が使われている場合は強制的に1枚と設定する + this._renderTextureCount = renderTextureCount < 1 ? 1 : renderTextureCount; + + this._clearedMaskBufferFlags = new Array(this._renderTextureCount); + + // クリッピングマスクを使う描画オブジェクトをすべて登録する + // クリッピングマスクは、通常数個程度に限定して使うものとする + this._clippingContextListForDraw.length = model.getDrawableCount(); + for (let i = 0; i < model.getDrawableCount(); i++) { + if (model.getDrawableMaskCounts()[i] <= 0) { + // クリッピングマスクが使用されていないアートメッシュ(多くの場合使用しない) + this._clippingContextListForDraw[i] = null; + continue; + } + + // 既にあるClipContextと同じかチェックする + let clippingContext: T_ClippingContext = this.findSameClip( + model.getDrawableMasks()[i], + model.getDrawableMaskCounts()[i] + ); + if (clippingContext == null) { + // 同一のマスクが存在していない場合は生成する + + clippingContext = new this._clippingContexttConstructor( + this, + model.getDrawableMasks()[i], + model.getDrawableMaskCounts()[i] + ); + this._clippingContextListForMask.push(clippingContext); + } + + clippingContext.addClippedDrawable(i); + + this._clippingContextListForDraw[i] = clippingContext; + } + } + + /** + * オフスクリーン用の初期化処理 + * + * @param model モデルのインスタンス + * @param maskBufferCount オフスクリーン用のマスクバッファの数 + */ + public initializeForOffscreen( + model: CubismModel, + maskBufferCount: number + ): void { + this._renderTextureCount = maskBufferCount; + + // レンダーテクスチャのクリアフラグの設定 + this._clearedMaskBufferFlags.length = this._renderTextureCount; + for (let i = 0; i < this._renderTextureCount; ++i) { + this._clearedMaskBufferFlags[i] = false; + } + + //クリッピングマスクを使う描画オブジェクトを全て登録する + //クリッピングマスクは、通常数個程度に限定して使うものとする + this._clippingContextListForOffscreen.length = model.getOffscreenCount(); + for (let i = 0; i < model.getOffscreenCount(); ++i) { + if (model.getOffscreenMaskCounts()[i] <= 0) { + //クリッピングマスクが使用されていないオフスクリーン(多くの場合使用しない) + this._clippingContextListForOffscreen.push(null); + continue; + } + + // 既にあるClipContextと同じかチェックする + let cc = this.findSameClip( + model.getOffscreenMasks()[i], + model.getOffscreenMaskCounts()[i] + ); + if (cc == null) { + // 同一のマスクが存在していない場合は生成する + cc = new this._clippingContexttConstructor( + this, + model.getOffscreenMasks()[i], + model.getOffscreenMaskCounts()[i] + ); + this._clippingContextListForMask.push(cc); + } + + cc.addClippedOffscreen(i); + + this._clippingContextListForOffscreen[i] = cc; + } + } + + /** + * 既にマスクを作っているかを確認 + * 作っている様であれば該当するクリッピングマスクのインスタンスを返す + * 作っていなければNULLを返す + * @param drawableMasks 描画オブジェクトをマスクする描画オブジェクトのリスト + * @param drawableMaskCounts 描画オブジェクトをマスクする描画オブジェクトの数 + * @return 該当するクリッピングマスクが存在すればインスタンスを返し、なければNULLを返す + */ + public findSameClip( + drawableMasks: Int32Array, + drawableMaskCounts: number + ): T_ClippingContext { + // 作成済みClippingContextと一致するか確認 + for (let i = 0; i < this._clippingContextListForMask.length; i++) { + const clippingContext: T_ClippingContext = + this._clippingContextListForMask[i]; + const count: number = clippingContext._clippingIdCount; + + // 個数が違う場合は別物 + if (count != drawableMaskCounts) { + continue; + } + + let sameCount = 0; + + // 同じIDを持つか確認。配列の数が同じなので、一致した個数が同じなら同じ物を持つとする + for (let j = 0; j < count; j++) { + const clipId: number = clippingContext._clippingIdList[j]; + + for (let k = 0; k < count; k++) { + if (drawableMasks[k] == clipId) { + sameCount++; + break; + } + } + } + + if (sameCount == count) { + return clippingContext; + } + } + + return null; // 見つからなかった + } + + /** + * 高精細マスク処理用の行列を計算する + * @param model モデルのインスタンス + * @param isRightHanded 処理が右手系であるか + */ + public setupMatrixForHighPrecision( + model: CubismModel, + isRightHanded: boolean + ): void { + // 全てのクリッピングを用意する + // 同じクリップ(複数の場合はまとめて一つのクリップ)を使う場合は1度だけ設定する + let usingClipCount = 0; + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.length; + clipIndex++ + ) { + // 1つのクリッピングマスクに関して + const cc: T_ClippingContext = this._clippingContextListForMask[clipIndex]; + + // このクリップを利用する描画オブジェクト群全体を囲む矩形を計算 + this.calcClippedDrawableTotalBounds(model, cc); + + if (cc._isUsing) { + usingClipCount++; // 使用中としてカウント + } + } + + // マスク行列作成処理 + if (usingClipCount > 0) { + this.setupLayoutBounds(0); + + // サイズがレンダーテクスチャの枚数と合わない場合は合わせる + if (this._clearedMaskBufferFlags.length != this._renderTextureCount) { + this._clearedMaskBufferFlags.length = this._renderTextureCount; + for (let i = 0; i < this._renderTextureCount; i++) { + this._clearedMaskBufferFlags[i] = false; + } + } else { + // マスクのクリアフラグを毎フレーム開始時に初期化 + for (let i = 0; i < this._renderTextureCount; i++) { + this._clearedMaskBufferFlags[i] = false; + } + } + + // 実際にマスクを生成する + // 全てのマスクをどの様にレイアウトして描くかを決定し、ClipContext , ClippedDrawContext に記憶する + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.length; + clipIndex++ + ) { + // --- 実際に1つのマスクを描く --- + const clipContext: T_ClippingContext = + this._clippingContextListForMask[clipIndex]; + const allClippedDrawRect: csmRect = clipContext._allClippedDrawRect; //このマスクを使う、全ての描画オブジェクトの論理座標上の囲み矩形 + const layoutBoundsOnTex01 = clipContext._layoutBounds; //この中にマスクを収める + const margin = 0.05; + let scaleX = 0.0; + let scaleY = 0.0; + const ppu: number = model.getPixelsPerUnit(); + const maskPixelSize: number = clipContext + .getClippingManager() + .getClippingMaskBufferSize(); + const physicalMaskWidth: number = + layoutBoundsOnTex01.width * maskPixelSize; + const physicalMaskHeight: number = + layoutBoundsOnTex01.height * maskPixelSize; + + this._tmpBoundsOnModel.setRect(allClippedDrawRect); + if (this._tmpBoundsOnModel.width * ppu > physicalMaskWidth) { + this._tmpBoundsOnModel.expand(allClippedDrawRect.width * margin, 0.0); + scaleX = layoutBoundsOnTex01.width / this._tmpBoundsOnModel.width; + } else { + scaleX = ppu / physicalMaskWidth; + } + + if (this._tmpBoundsOnModel.height * ppu > physicalMaskHeight) { + this._tmpBoundsOnModel.expand( + 0.0, + allClippedDrawRect.height * margin + ); + scaleY = layoutBoundsOnTex01.height / this._tmpBoundsOnModel.height; + } else { + scaleY = ppu / physicalMaskHeight; + } + + // マスク生成時に使う行列を求める + this.createMatrixForMask( + isRightHanded, + layoutBoundsOnTex01, + scaleX, + scaleY + ); + + clipContext._matrixForMask.setMatrix(this._tmpMatrixForMask.getArray()); + clipContext._matrixForDraw.setMatrix(this._tmpMatrixForDraw.getArray()); + } + } + } + + /** + * オフスクリーンの高精細マスク処理用の行列を計算する + * + * @param model モデルのインスタンス + * @param isRightHanded 処理が右手系であるか + * @param mvp モデルビュー投影行列 + */ + public setupMatrixForOffscreenHighPrecision( + model: CubismModel, + isRightHanded: boolean, + mvp: CubismMatrix44 + ): void { + // 全てのクリッピングを用意する + // 同じクリップ(複数の場合はまとめて1つのクリップ)を使う場合は1度だけ設定する + let usingClipCount = 0; + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.length; + clipIndex++ + ) { + // 1つのクリッピングマスクに関して + const cc: T_ClippingContext = this._clippingContextListForMask[clipIndex]; + + // このクリップを利用する描画オブジェクト群全体を囲む矩形を計算 + this.calcClippedOffscreenTotalBounds(model, cc); + + if (cc._isUsing) { + usingClipCount++; //使用中としてカウント + } + } + + if (usingClipCount <= 0) { + return; + } + // マスク行列作成処理 + this.setupLayoutBounds(0); + + // サイズがレンダーテクスチャの枚数と合わない場合は合わせる + if (this._clearedMaskBufferFlags.length != this._renderTextureCount) { + this._clearedMaskBufferFlags.length = this._renderTextureCount; + + for (let i = 0; i < this._renderTextureCount; ++i) { + this._clearedMaskBufferFlags[i] = false; + } + } else { + // マスクのクリアフラグを毎フレーム開始時に初期化 + for (let i = 0; i < this._renderTextureCount; ++i) { + this._clearedMaskBufferFlags[i] = false; + } + } + + // 実際にマスクを生成する + // 全てのマスクをどの様にレイアウトして描くかを決定し、ClipContext , ClippedDrawContext に記憶する + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.length; + clipIndex++ + ) { + // --- 実際に1つのマスクを描く --- + const clipContext = this._clippingContextListForMask[clipIndex]; + const allClippedDrawRect = clipContext._allClippedDrawRect; //このマスクを使う、全ての描画オブジェクトの論理座標上の囲み矩形 + const layoutBoundsOnTex01 = clipContext._layoutBounds; //この中にマスクを収める + const margin = 0.05; + let scaleX = 0.0; + let scaleY = 0.0; + const ppu = model.getPixelsPerUnit(); + const maskPixel = clipContext + .getClippingManager() + .getClippingMaskBufferSize(); + const physicalMaskWidth = layoutBoundsOnTex01.width * maskPixel; + const physicalMaskHeight = layoutBoundsOnTex01.height * maskPixel; + + this._tmpBoundsOnModel.setRect(allClippedDrawRect); + if (this._tmpBoundsOnModel.width * ppu > physicalMaskWidth) { + this._tmpBoundsOnModel.expand(allClippedDrawRect.width * margin, 0.0); + scaleX = layoutBoundsOnTex01.width / this._tmpBoundsOnModel.width; + } else { + scaleX = ppu / physicalMaskWidth; + } + + if (this._tmpBoundsOnModel.height * ppu > physicalMaskHeight) { + this._tmpBoundsOnModel.expand(0.0, allClippedDrawRect.height * margin); + scaleY = layoutBoundsOnTex01.height / this._tmpBoundsOnModel.height; + } else { + scaleY = ppu / physicalMaskHeight; + } + + // マスク生成時に使う行列を求める + this.createMatrixForMask( + isRightHanded, + layoutBoundsOnTex01, + scaleX, + scaleY + ); + + clipContext._matrixForMask.setMatrix(this._tmpMatrixForMask.getArray()); + clipContext._matrixForDraw.setMatrix(this._tmpMatrixForDraw.getArray()); + + // clipContext * mvp^-1 + const invertMvp = mvp.getInvert(); + clipContext._matrixForDraw.multiplyByMatrix(invertMvp); + } + } + + /** + * マスクを使う描画オブジェクトの全体の矩形を計算する。 + * + * @param model モデルのインスタンス + * @param clippingContext クリッピングコンテキスト + */ + public calcClippedOffscreenTotalBounds( + model: CubismModel, + clippingContext: T_ClippingContext + ): void { + // 被クリッピングマスク(マスクされる描画オブジェクト)の全体の矩形 + let clippedDrawTotalMinX = Number.MAX_VALUE, + clippedDrawTotalMinY = Number.MAX_VALUE; + let clippedDrawTotalMaxX = -Number.MAX_VALUE, + clippedDrawTotalMaxY = -Number.MAX_VALUE; + + // このマスクが実際に必要か判定する + // このクリッピングを利用する「描画オブジェクト」がひとつでも使用可能であればマスクを生成する必要がある + const clippedOffscreenCount = + clippingContext._clippedOffscreenIndexList.length; + + const clippedOffscreenChildDrawableIndexList = new Array(); + for ( + let clippedOffscreenIndex = 0; + clippedOffscreenIndex < clippedOffscreenCount; + clippedOffscreenIndex++ + ) { + // マスクを使用する描画オブジェクトの描画される矩形を求める + const offscreenIndex = + clippingContext._clippedOffscreenIndexList[clippedOffscreenIndex]; + + this.getOffscreenChildDrawableIndexList( + model, + offscreenIndex, + clippedOffscreenChildDrawableIndexList + ); + } + + const childDrawableCount = clippedOffscreenChildDrawableIndexList.length; + for ( + let childDrawableIndex = 0; + childDrawableIndex < childDrawableCount; + childDrawableIndex++ + ) { + const drawableVertexCount = model.getDrawableVertexCount( + clippedOffscreenChildDrawableIndexList[childDrawableIndex] + ); + const drawableVertexes = model.getDrawableVertices( + clippedOffscreenChildDrawableIndexList[childDrawableIndex] + ); + + let minX = Number.MAX_VALUE, + minY = Number.MAX_VALUE; + let maxX = -Number.MAX_VALUE, + maxY = -Number.MAX_VALUE; + + const loop = drawableVertexCount * Constant.vertexStep; + for ( + let pi = Constant.vertexOffset; + pi < loop; + pi += Constant.vertexStep + ) { + const x = drawableVertexes[pi]; + const y = drawableVertexes[pi + 1]; + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; + } + + if (minX == Number.MAX_VALUE) continue; //有効な点がひとつも取れなかったのでスキップする + + // 全体の矩形に反映 + if (minX < clippedDrawTotalMinX) clippedDrawTotalMinX = minX; + if (minY < clippedDrawTotalMinY) clippedDrawTotalMinY = minY; + if (maxX > clippedDrawTotalMaxX) clippedDrawTotalMaxX = maxX; + if (maxY > clippedDrawTotalMaxY) clippedDrawTotalMaxY = maxY; + } + + if (clippedDrawTotalMinX == Number.MAX_VALUE) { + clippingContext._allClippedDrawRect.x = 0.0; + clippingContext._allClippedDrawRect.y = 0.0; + clippingContext._allClippedDrawRect.width = 0.0; + clippingContext._allClippedDrawRect.height = 0.0; + clippingContext._isUsing = false; + } else { + clippingContext._isUsing = true; + const w = clippedDrawTotalMaxX - clippedDrawTotalMinX; + const h = clippedDrawTotalMaxY - clippedDrawTotalMinY; + clippingContext._allClippedDrawRect.x = clippedDrawTotalMinX; + clippingContext._allClippedDrawRect.y = clippedDrawTotalMinY; + clippingContext._allClippedDrawRect.width = w; + clippingContext._allClippedDrawRect.height = h; + } + } + + /** + * マスクを使う描画オブジェクトの全体の矩形を計算する。 + * + * @param model モデルのインスタンス + * @param offscreenIndex オフスクリーンのインデックス + * @param childDrawableIndexList オフスクリーンの子Drawableのインデックスリスト + */ + public getOffscreenChildDrawableIndexList( + model: CubismModel, + offscreenIndex: number, + childDrawableIndexList: Array + ): void { + // 親オブジェクトを取得 + const ownerIndex = model.getOffscreenOwnerIndices()[offscreenIndex]; + + // パーツのみ + this.getPartChildDrawableIndexList( + model, + ownerIndex, + childDrawableIndexList + ); + } + + /** + * パーツの子Drawableのインデックスリストを取得する。 + * + * @param model モデルのインスタンス + * @param partIndex パーツのインデックス + * @param childDrawableIndexList パーツの子Drawableのインデックスリスト + */ + public getPartChildDrawableIndexList( + model: CubismModel, + partIndex: number, + childDrawableIndexList: Array + ): void { + const childDrawObjects = + model.getPartsHierarchy()[partIndex].childDrawObjects; + childDrawableIndexList.push(...childDrawObjects.drawableIndices); + + for (let i = 0; i < childDrawObjects.offscreenIndices.length; ++i) { + this.getOffscreenChildDrawableIndexList( + model, + childDrawObjects.offscreenIndices[i], + childDrawableIndexList + ); + } + } + + /** + * マスク作成・描画用の行列を作成する。 + * @param isRightHanded 座標を右手系として扱うかを指定 + * @param layoutBoundsOnTex01 マスクを収める領域 + * @param scaleX 描画オブジェクトの伸縮率 + * @param scaleY 描画オブジェクトの伸縮率 + */ + public createMatrixForMask( + isRightHanded: boolean, + layoutBoundsOnTex01: csmRect, + scaleX: number, + scaleY: number + ): void { + this._tmpMatrix.loadIdentity(); + { + // Layout0..1 を -1..1に変換 + this._tmpMatrix.translateRelative(-1.0, -1.0); + this._tmpMatrix.scaleRelative(2.0, 2.0); + } + { + // view to Layout0..1 + this._tmpMatrix.translateRelative( + layoutBoundsOnTex01.x, + layoutBoundsOnTex01.y + ); //new = [translate] + this._tmpMatrix.scaleRelative(scaleX, scaleY); //new = [translate][scale] + this._tmpMatrix.translateRelative( + -this._tmpBoundsOnModel.x, + -this._tmpBoundsOnModel.y + ); //new = [translate][scale][translate] + } + // tmpMatrixForMask が計算結果 + this._tmpMatrixForMask.setMatrix(this._tmpMatrix.getArray()); + + this._tmpMatrix.loadIdentity(); + { + this._tmpMatrix.translateRelative( + layoutBoundsOnTex01.x, + layoutBoundsOnTex01.y * (isRightHanded ? -1.0 : 1.0) + ); //new = [translate] + this._tmpMatrix.scaleRelative( + scaleX, + scaleY * (isRightHanded ? -1.0 : 1.0) + ); //new = [translate][scale] + this._tmpMatrix.translateRelative( + -this._tmpBoundsOnModel.x, + -this._tmpBoundsOnModel.y + ); //new = [translate][scale][translate] + } + + this._tmpMatrixForDraw.setMatrix(this._tmpMatrix.getArray()); + } + + /** + * クリッピングコンテキストを配置するレイアウト + * 指定された数のレンダーテクスチャを極力いっぱいに使ってマスクをレイアウトする + * マスクグループの数が4以下ならRGBA各チャンネルに一つずつマスクを配置し、5以上6以下ならRGBAを2,2,1,1と配置する。 + * + * @param usingClipCount 配置するクリッピングコンテキストの数 + */ + public setupLayoutBounds(usingClipCount: number): void { + const useClippingMaskMaxCount = + this._renderTextureCount <= 1 + ? ClippingMaskMaxCountOnDefault + : ClippingMaskMaxCountOnMultiRenderTexture * this._renderTextureCount; + + if (usingClipCount <= 0 || usingClipCount > useClippingMaskMaxCount) { + if (usingClipCount > useClippingMaskMaxCount) { + // マスクの制限数の警告を出す + CubismLogError( + 'not supported mask count : {0}\n[Details] render texture count : {1}, mask count : {2}', + usingClipCount - useClippingMaskMaxCount, + this._renderTextureCount, + usingClipCount + ); + } + // この場合は一つのマスクターゲットを毎回クリアして使用する + for ( + let index = 0; + index < this._clippingContextListForMask.length; + index++ + ) { + const clipContext: T_ClippingContext = + this._clippingContextListForMask[index]; + clipContext._layoutChannelIndex = 0; // どうせ毎回消すので固定 + clipContext._layoutBounds.x = 0.0; + clipContext._layoutBounds.y = 0.0; + clipContext._layoutBounds.width = 1.0; + clipContext._layoutBounds.height = 1.0; + clipContext._bufferIndex = 0; + } + return; + } + + // レンダーテクスチャが1枚なら9分割する(最大36枚) + const layoutCountMaxValue = this._renderTextureCount <= 1 ? 9 : 8; + + // 指定された数のレンダーテクスチャを極力いっぱいに使ってマスクをレイアウトする(デフォルトなら1)。 + // マスクグループの数が4以下ならRGBA各チャンネルに1つずつマスクを配置し、5以上6以下ならRGBAを2,2,1,1と配置する。 + let countPerSheetDiv: number = usingClipCount / this._renderTextureCount; // レンダーテクスチャ1枚あたり何枚割り当てるか。 + const reduceLayoutTextureCount: number = + usingClipCount % this._renderTextureCount; // レイアウトの数を1枚減らすレンダーテクスチャの数(この数だけのレンダーテクスチャが対象)。 + + // 1枚に割り当てるマスクの分割数を取りたいため、小数点は切り上げる + countPerSheetDiv = Math.ceil(countPerSheetDiv); + + // RGBAを順番に使っていく + let divCount: number = countPerSheetDiv / ColorChannelCount; // 1チャンネルに配置する基本のマスク + const modCount: number = countPerSheetDiv % ColorChannelCount; // 余り、この番号のチャンネルまでに一つずつ配分する(インデックスではない) + + // 小数点は切り捨てる + divCount = ~~divCount; + + // RGBAそれぞれのチャンネルを用意していく(0:R, 1:G, 2:B, 3:A) + let curClipIndex = 0; // 順番に設定していく + + for ( + let renderTextureIndex = 0; + renderTextureIndex < this._renderTextureCount; + renderTextureIndex++ + ) { + for ( + let channelIndex = 0; + channelIndex < ColorChannelCount; + channelIndex++ + ) { + // このチャンネルにレイアウトする数 + // NOTE: レイアウト数 = 1チャンネルに配置する基本のマスク + 余りのマスクを置くチャンネルなら1つ追加 + let layoutCount: number = divCount + (channelIndex < modCount ? 1 : 0); + + // レイアウトの数を1枚減らす場合にそれを行うチャンネルを決定 + // divが0の時は正常なインデックスの範囲内になるように調整 + const checkChannelIndex = modCount + (divCount < 1 ? -1 : 0); + + // 今回が対象のチャンネルかつ、レイアウトの数を1枚減らすレンダーテクスチャが存在する場合 + if (channelIndex == checkChannelIndex && reduceLayoutTextureCount > 0) { + // 現在のレンダーテクスチャが、対象のレンダーテクスチャであればレイアウトの数を1枚減らす。 + layoutCount -= !(renderTextureIndex < reduceLayoutTextureCount) + ? 1 + : 0; + } + + // 分割方法を決定する + if (layoutCount == 0) { + // 何もしない + } else if (layoutCount == 1) { + // 全てをそのまま使う + const clipContext: T_ClippingContext = + this._clippingContextListForMask[curClipIndex++]; + clipContext._layoutChannelIndex = channelIndex; + clipContext._layoutBounds.x = 0.0; + clipContext._layoutBounds.y = 0.0; + clipContext._layoutBounds.width = 1.0; + clipContext._layoutBounds.height = 1.0; + clipContext._bufferIndex = renderTextureIndex; + } else if (layoutCount == 2) { + for (let i = 0; i < layoutCount; i++) { + let xpos: number = i % 2; + + // 小数点は切り捨てる + xpos = ~~xpos; + + const cc: T_ClippingContext = + this._clippingContextListForMask[curClipIndex++]; + cc._layoutChannelIndex = channelIndex; + + // UVを2つに分解して使う + cc._layoutBounds.x = xpos * 0.5; + cc._layoutBounds.y = 0.0; + cc._layoutBounds.width = 0.5; + cc._layoutBounds.height = 1.0; + cc._bufferIndex = renderTextureIndex; + } + } else if (layoutCount <= 4) { + // 4分割して使う + for (let i = 0; i < layoutCount; i++) { + let xpos: number = i % 2; + let ypos: number = i / 2; + + // 小数点は切り捨てる + xpos = ~~xpos; + ypos = ~~ypos; + + const cc = this._clippingContextListForMask[curClipIndex++]; + cc._layoutChannelIndex = channelIndex; + + cc._layoutBounds.x = xpos * 0.5; + cc._layoutBounds.y = ypos * 0.5; + cc._layoutBounds.width = 0.5; + cc._layoutBounds.height = 0.5; + cc._bufferIndex = renderTextureIndex; + } + } else if (layoutCount <= layoutCountMaxValue) { + // 9分割して使う + for (let i = 0; i < layoutCount; i++) { + let xpos = i % 3; + let ypos = i / 3; + + // 小数点は切り捨てる + xpos = ~~xpos; + ypos = ~~ypos; + + const cc: T_ClippingContext = + this._clippingContextListForMask[curClipIndex++]; + cc._layoutChannelIndex = channelIndex; + + cc._layoutBounds.x = xpos / 3.0; + cc._layoutBounds.y = ypos / 3.0; + cc._layoutBounds.width = 1.0 / 3.0; + cc._layoutBounds.height = 1.0 / 3.0; + cc._bufferIndex = renderTextureIndex; + } + } else { + // マスクの制限枚数を超えた場合の処理 + CubismLogError( + 'not supported mask count : {0}\n[Details] render texture count : {1}, mask count : {2}', + usingClipCount - useClippingMaskMaxCount, + this._renderTextureCount, + usingClipCount + ); + + // SetupShaderProgramでオーバーアクセスが発生するので仮で数値を入れる + // もちろん描画結果は正しいものではなくなる + for (let index = 0; index < layoutCount; index++) { + const cc: T_ClippingContext = + this._clippingContextListForMask[curClipIndex++]; + + cc._layoutChannelIndex = 0; + + cc._layoutBounds.x = 0.0; + cc._layoutBounds.y = 0.0; + cc._layoutBounds.width = 1.0; + cc._layoutBounds.height = 1.0; + cc._bufferIndex = 0; + } + } + } + } + } + + /** + * マスクされる描画オブジェクト群全体を囲む矩形(モデル座標系)を計算する + * @param model モデルのインスタンス + * @param clippingContext クリッピングマスクのコンテキスト + */ + public calcClippedDrawableTotalBounds( + model: CubismModel, + clippingContext: T_ClippingContext + ): void { + // 被クリッピングマスク(マスクされる描画オブジェクト)の全体の矩形 + let clippedDrawTotalMinX: number = Number.MAX_VALUE; + let clippedDrawTotalMinY: number = Number.MAX_VALUE; + let clippedDrawTotalMaxX: number = Number.MIN_VALUE; + let clippedDrawTotalMaxY: number = Number.MIN_VALUE; + + // このマスクが実際に必要か判定する + // このクリッピングを利用する「描画オブジェクト」がひとつでも使用可能であればマスクを生成する必要がある + const clippedDrawCount: number = + clippingContext._clippedDrawableIndexList.length; + + for ( + let clippedDrawableIndex = 0; + clippedDrawableIndex < clippedDrawCount; + clippedDrawableIndex++ + ) { + // マスクを使用する描画オブジェクトの描画される矩形を求める + const drawableIndex: number = + clippingContext._clippedDrawableIndexList[clippedDrawableIndex]; + + const drawableVertexCount: number = + model.getDrawableVertexCount(drawableIndex); + const drawableVertexes: Float32Array = + model.getDrawableVertices(drawableIndex); + + let minX: number = Number.MAX_VALUE; + let minY: number = Number.MAX_VALUE; + let maxX: number = -Number.MAX_VALUE; + let maxY: number = -Number.MAX_VALUE; + + const loop: number = drawableVertexCount * Constant.vertexStep; + for ( + let pi: number = Constant.vertexOffset; + pi < loop; + pi += Constant.vertexStep + ) { + const x: number = drawableVertexes[pi]; + const y: number = drawableVertexes[pi + 1]; + + if (x < minX) { + minX = x; + } + if (x > maxX) { + maxX = x; + } + if (y < minY) { + minY = y; + } + if (y > maxY) { + maxY = y; + } + } + + // 有効な点が一つも取れなかったのでスキップ + if (minX == Number.MAX_VALUE) { + continue; + } + + // 全体の矩形に反映 + if (minX < clippedDrawTotalMinX) { + clippedDrawTotalMinX = minX; + } + if (minY < clippedDrawTotalMinY) { + clippedDrawTotalMinY = minY; + } + if (maxX > clippedDrawTotalMaxX) { + clippedDrawTotalMaxX = maxX; + } + if (maxY > clippedDrawTotalMaxY) { + clippedDrawTotalMaxY = maxY; + } + + if (clippedDrawTotalMinX == Number.MAX_VALUE) { + clippingContext._allClippedDrawRect.x = 0.0; + clippingContext._allClippedDrawRect.y = 0.0; + clippingContext._allClippedDrawRect.width = 0.0; + clippingContext._allClippedDrawRect.height = 0.0; + clippingContext._isUsing = false; + } else { + clippingContext._isUsing = true; + const w: number = clippedDrawTotalMaxX - clippedDrawTotalMinX; + const h: number = clippedDrawTotalMaxY - clippedDrawTotalMinY; + clippingContext._allClippedDrawRect.x = clippedDrawTotalMinX; + clippingContext._allClippedDrawRect.y = clippedDrawTotalMinY; + clippingContext._allClippedDrawRect.width = w; + clippingContext._allClippedDrawRect.height = h; + } + } + } + + /** + * 画面描画に使用するクリッピングマスクのリストを取得する + * @return 画面描画に使用するクリッピングマスクのリスト + */ + public getClippingContextListForDraw(): Array { + return this._clippingContextListForDraw; + } + + public getClippingContextListForOffscreen(): Array { + return this._clippingContextListForOffscreen; + } + + /** + * クリッピングマスクバッファのサイズを取得する + * @return クリッピングマスクバッファのサイズ + */ + public getClippingMaskBufferSize(): number { + return this._clippingMaskBufferSize; + } + + /** + * このバッファのレンダーテクスチャの枚数を取得する + * @return このバッファのレンダーテクスチャの枚数 + */ + public getRenderTextureCount(): number { + return this._renderTextureCount; + } + + /** + * カラーチャンネル(RGBA)のフラグを取得する + * @param channelNo カラーチャンネル(RGBA)の番号(0:R, 1:G, 2:B, 3:A) + */ + public getChannelFlagAsColor(channelNo: number): CubismTextureColor { + return this._channelColors[channelNo]; + } + + /** + * クリッピングマスクバッファのサイズを設定する + * @param size クリッピングマスクバッファのサイズ + */ + public setClippingMaskBufferSize(size: number): void { + this._clippingMaskBufferSize = size; + } + + protected _clearedMaskBufferFlags: Array; //マスクのクリアフラグの配列 + + protected _channelColors: Array; + protected _clippingContextListForMask: Array; // マスク用クリッピングコンテキストのリスト + protected _clippingContextListForDraw: Array; // 描画用クリッピングコンテキストのリスト + protected _clippingContextListForOffscreen: Array; // オフスクリーン用クリッピングコンテキストのリスト + protected _clippingMaskBufferSize: number; // クリッピングマスクのバッファサイズ(初期値:256) + protected _renderTextureCount: number; // 生成するレンダーテクスチャの枚数 + + protected _tmpMatrix: CubismMatrix44; // マスク計算用の行列 + protected _tmpMatrixForMask: CubismMatrix44; // マスク計算用の行列 + protected _tmpMatrixForDraw: CubismMatrix44; // マスク計算用の行列 + protected _tmpBoundsOnModel: csmRect; // マスク配置計算用の矩形 + + protected _clippingContexttConstructor: ClippingContextConstructor; +} diff --git a/avatar-h5-renderer/framework/src/rendering/cubismoffscreenmanager.ts b/avatar-h5-renderer/framework/src/rendering/cubismoffscreenmanager.ts new file mode 100644 index 0000000..959a56a --- /dev/null +++ b/avatar-h5-renderer/framework/src/rendering/cubismoffscreenmanager.ts @@ -0,0 +1,591 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { updateSize } from '../utils/cubismarrayutils'; +import { CubismLogError } from '../utils/cubismdebug'; +import { CubismRenderTarget_WebGL } from './cubismrendertarget_webgl'; + +/** + * フレームバッファなどのコンテナのクラス + */ +class CubismRenderTargetContainer { + /** + * Constructor + * + * @param colorBuffer カラーバッファ + * @param renderTexture レンダーテクスチャ + * @param inUse 使用中かどうか + */ + public constructor( + colorBuffer: WebGLTexture = null, + renderTexture: WebGLFramebuffer = null, + inUse: boolean = false + ) { + this.colorBuffer = colorBuffer; + this.renderTexture = renderTexture; + this.inUse = inUse; + } + + public clear(): void { + this.colorBuffer = null; + this.renderTexture = null; + this.inUse = false; + } + + /** + * カラーバッファを取得 + * + * @returns カラーバッファ + */ + public getColorBuffer(): WebGLTexture { + return this.colorBuffer; + } + + /** + * レンダーテクスチャを取得 + * + * @returns レンダーテクスチャ + */ + public getRenderTexture(): WebGLFramebuffer { + return this.renderTexture; + } + + public colorBuffer: WebGLTexture; // colorBuffer + public renderTexture: WebGLFramebuffer; // renderTarget + public inUse: boolean; // Whether this container's render target is currently in use +} + +/** + * WebGLContextごとのリソース管理を行う内部クラス + */ +class CubismWebGLContextManager { + constructor(gl: WebGLRenderingContext | WebGL2RenderingContext) { + this.gl = gl; + this.offscreenRenderTargetContainers = + new Array(); + this.previousActiveRenderTextureMaxCount = 0; + this.currentActiveRenderTextureCount = 0; + this.hasResetThisFrame = false; + this.width = 0; + this.height = 0; + } + + public release(): void { + if (this.offscreenRenderTargetContainers != null) { + for ( + let index = 0; + index < this.offscreenRenderTargetContainers.length; + ++index + ) { + const container = this.offscreenRenderTargetContainers[index]; + this.gl.deleteTexture(container.colorBuffer); + this.gl.deleteFramebuffer(container.renderTexture); + } + this.offscreenRenderTargetContainers.length = 0; + this.offscreenRenderTargetContainers = null; + } + } + + public gl: WebGLRenderingContext | WebGL2RenderingContext; // WebGLContext + public offscreenRenderTargetContainers: Array; // オフスクリーン描画用レンダーターゲットのリスト + public previousActiveRenderTextureMaxCount: number; // 直前のアクティブなレンダーターゲットの最大数 + public currentActiveRenderTextureCount: number; // 現在のアクティブなレンダーターゲットの数 + public hasResetThisFrame: boolean; // 今フレームでリセットされたかどうか + public width: number; // 幅 + public height: number; // 高さ +} + +/** + * WebGL用オフスクリーン描画機能を管理するマネージャ + * オフスクリーン描画機能に必要なフレームバッファなどを含むコンテナを管理する。 + * 複数のWebGLContextに対応。 + */ +export class CubismWebGLOffscreenManager { + /** + * コンストラクタ + */ + private constructor() { + this._contextManagers = new Map< + WebGLRenderingContext | WebGL2RenderingContext, + CubismWebGLContextManager + >(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._contextManagers != null) { + for (const manager of this._contextManagers.values()) { + manager.release(); + } + this._contextManagers.clear(); + this._contextManagers = null; + } + CubismWebGLOffscreenManager._instance = null; + } + + /** + * インスタンスの取得 + * + * @return インスタンス + */ + public static getInstance(): CubismWebGLOffscreenManager { + if (this._instance == null) { + this._instance = new CubismWebGLOffscreenManager(); + } + + return this._instance; + } + + /** + * WebGLContextに対応するマネージャーを取得または作成 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @return WebGLContextManager + */ + private getContextManager( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): CubismWebGLContextManager { + if (!this._contextManagers.has(gl)) { + this._contextManagers.set(gl, new CubismWebGLContextManager(gl)); + } + return this._contextManagers.get(gl); + } + + /** + * 指定されたWebGLContextのマネージャーを削除 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + */ + public removeContext( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): void { + if (this._contextManagers.has(gl)) { + const manager = this._contextManagers.get(gl); + manager.release(); + this._contextManagers.delete(gl); + } + } + + /** + * 初期化処理 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @param width 幅 + * @param height 高さ + */ + public initialize( + gl: WebGLRenderingContext | WebGL2RenderingContext, + width: number, + height: number + ): void { + const contextManager = this.getContextManager(gl); + + // initialize offscreenRenderTargetContainers + if (contextManager.offscreenRenderTargetContainers != null) { + for ( + let index = 0; + index < contextManager.offscreenRenderTargetContainers.length; + ++index + ) { + const container = contextManager.offscreenRenderTargetContainers[index]; + contextManager.gl.deleteTexture(container.colorBuffer); + contextManager.gl.deleteFramebuffer(container.renderTexture); + container.clear(); + } + contextManager.offscreenRenderTargetContainers.length = 0; + } else { + contextManager.offscreenRenderTargetContainers = + new Array(); + } + + contextManager.width = width; + contextManager.height = height; + contextManager.previousActiveRenderTextureMaxCount = 0; + contextManager.currentActiveRenderTextureCount = 0; + contextManager.hasResetThisFrame = false; + } + + /** + * モデルを描画する前に呼び出すフレーム開始時の処理を行う + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + */ + public beginFrameProcess( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): void { + const contextManager = this.getContextManager(gl); + if (contextManager.hasResetThisFrame) { + return; + } + contextManager.previousActiveRenderTextureMaxCount = 0; + contextManager.hasResetThisFrame = true; + } + + /** + * モデルの描画が終わった後に呼び出すフレーム終了時の処理 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + */ + public endFrameProcess( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): void { + const contextManager = this.getContextManager(gl); + contextManager.hasResetThisFrame = false; + } + + /** + * コンテナサイズの取得 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + */ + public getContainerSize( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): number { + const contextManager = this.getContextManager(gl); + if (contextManager.offscreenRenderTargetContainers == null) { + return 0; + } + return contextManager.offscreenRenderTargetContainers.length; + } + + /** + * 使用可能なリソースコンテナの取得 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @param width 幅 + * @param height 高さ + * @param previousFramebuffer 前のフレームバッファ + * @return 使用可能なリソースコンテナ + */ + public getOffscreenRenderTargetContainers( + gl: WebGLRenderingContext | WebGL2RenderingContext, + width: number, + height: number, + previousFramebuffer: WebGLFramebuffer + ): CubismRenderTargetContainer { + const contextManager = this.getContextManager(gl); + + // コンテナが初期化されていないか、サイズが変わったら初期化し直す + if ( + contextManager.width != width || + contextManager.height != height || + contextManager.offscreenRenderTargetContainers == null + ) { + this.initialize(gl, width, height); + } + + // 使用数を更新 + this.updateRenderTargetContainerCount(gl); + + // 使われていないリソースコンテナがあればそれを返す + const container = this.getUnusedOffscreenRenderTargetContainer(gl); + if (container != null) { + return container; + } + + // 使われていないリソースコンテナがなければ新たに作成する + const offscreenRenderTextureContainer = + this.createOffscreenRenderTargetContainer( + gl, + width, + height, + previousFramebuffer + ); + + return offscreenRenderTextureContainer; + } + + /** + * リソースコンテナの使用状態を取得 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @param renderTexture WebGLFramebuffer + * @return 使用中はtrue、未使用の場合はfalse + */ + public getUsingRenderTextureState( + gl: WebGLRenderingContext | WebGL2RenderingContext, + renderTexture: WebGLFramebuffer + ): boolean { + const contextManager = this.getContextManager(gl); + for ( + let index = 0; + index < contextManager.offscreenRenderTargetContainers.length; + ++index + ) { + if ( + contextManager.offscreenRenderTargetContainers[index].renderTexture == + renderTexture + ) { + return contextManager.offscreenRenderTargetContainers[index].inUse; + } + } + return true; + } + + /** + * リソースコンテナの使用を開始する。 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @param renderTexture WebGLFramebuffer + */ + public startUsingRenderTexture( + gl: WebGLRenderingContext | WebGL2RenderingContext, + renderTexture: WebGLFramebuffer + ): void { + const contextManager = this.getContextManager(gl); + for ( + let index = 0; + index < contextManager.offscreenRenderTargetContainers.length; + ++index + ) { + if ( + contextManager.offscreenRenderTargetContainers[index].renderTexture != + renderTexture + ) { + continue; + } + + contextManager.offscreenRenderTargetContainers[index].inUse = true; + + this.updateRenderTargetContainerCount(gl); + + break; + } + } + + /** + * リソースコンテナの使用を終了する。 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @param renderTexture WebGLFramebuffer + */ + public stopUsingRenderTexture( + gl: WebGLRenderingContext | WebGL2RenderingContext, + renderTexture: WebGLFramebuffer + ): void { + const contextManager = this.getContextManager(gl); + for ( + let index = 0; + index < contextManager.offscreenRenderTargetContainers.length; + ++index + ) { + if ( + contextManager.offscreenRenderTargetContainers[index].renderTexture != + renderTexture + ) { + continue; + } + + contextManager.offscreenRenderTargetContainers[index].inUse = false; + + contextManager.currentActiveRenderTextureCount--; + if (contextManager.currentActiveRenderTextureCount < 0) { + contextManager.currentActiveRenderTextureCount = 0; + } + break; + } + } + + /** + * リソースコンテナの使用を全て終了する。 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + */ + public stopUsingAllRenderTextures( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): void { + const contextManager = this.getContextManager(gl); + for ( + let index = 0; + index < contextManager.offscreenRenderTargetContainers.length; + ++index + ) { + contextManager.offscreenRenderTargetContainers[index].inUse = false; + } + + contextManager.currentActiveRenderTextureCount = 0; + } + + /** + * 使用されていないリソースコンテナを解放する。 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + */ + public releaseStaleRenderTextures( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): void { + const contextManager = this.getContextManager(gl); + const listSize = contextManager.offscreenRenderTargetContainers.length; + + if (contextManager.hasResetThisFrame || listSize === 0) { + // 使用する量が変化する場合は開放しない + return; + } + + // 未使用な場所を開放して直前の最大数までリサイズする + let findPos = 0; + let resize = contextManager.previousActiveRenderTextureMaxCount; + for ( + let i = listSize; + contextManager.previousActiveRenderTextureMaxCount < i; + --i + ) { + const index = i - 1; + if (contextManager.offscreenRenderTargetContainers[index].inUse) { + // 空いている場所探して移動させる + let isFind = false; + for ( + ; + findPos < contextManager.previousActiveRenderTextureMaxCount; + ++findPos + ) { + if (!contextManager.offscreenRenderTargetContainers[findPos].inUse) { + const tempContainer = + contextManager.offscreenRenderTargetContainers[findPos]; + contextManager.offscreenRenderTargetContainers[findPos] = + contextManager.offscreenRenderTargetContainers[index]; + contextManager.offscreenRenderTargetContainers[findPos].inUse = + true; + contextManager.offscreenRenderTargetContainers[index] = + tempContainer; + contextManager.offscreenRenderTargetContainers[index].inUse = false; + isFind = true; + break; + } + } + if (!isFind) { + // 空いている場所が見つからなかったら現状のサイズでリサイズする + resize = i; + break; + } + } + const container = contextManager.offscreenRenderTargetContainers[index]; + contextManager.gl.bindTexture(contextManager.gl.TEXTURE_2D, null); + contextManager.gl.deleteTexture(container.colorBuffer); + contextManager.gl.bindFramebuffer(contextManager.gl.FRAMEBUFFER, null); + contextManager.gl.deleteFramebuffer(container.renderTexture); + container.clear(); + } + updateSize(contextManager.offscreenRenderTargetContainers, resize); + } + + /** + * 直前のアクティブなレンダーターゲットの最大数を取得 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @returns 直前のアクティブなレンダーターゲットの最大数 + */ + public getPreviousActiveRenderTextureCount( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): number { + const contextManager = this.getContextManager(gl); + return contextManager.previousActiveRenderTextureMaxCount; + } + + /** + * 現在のアクティブなレンダーターゲットの数を取得 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @returns 現在のアクティブなレンダーターゲットの数 + */ + public getCurrentActiveRenderTextureCount( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): number { + const contextManager = this.getContextManager(gl); + return contextManager.currentActiveRenderTextureCount; + } + + /** + * 現在のアクティブなレンダーターゲットの数を更新 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + */ + public updateRenderTargetContainerCount( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): void { + const contextManager = this.getContextManager(gl); + ++contextManager.currentActiveRenderTextureCount; + + // 最大数更新 + contextManager.previousActiveRenderTextureMaxCount = + contextManager.currentActiveRenderTextureCount > + contextManager.previousActiveRenderTextureMaxCount + ? contextManager.currentActiveRenderTextureCount + : contextManager.previousActiveRenderTextureMaxCount; + } + + /** + * 使用されていないリソースコンテナの取得 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @return 使用されていないリソースコンテナ + */ + public getUnusedOffscreenRenderTargetContainer( + gl: WebGLRenderingContext | WebGL2RenderingContext + ): CubismRenderTargetContainer { + const contextManager = this.getContextManager(gl); + // 使われていないリソースコンテナがあればそれを返す + for ( + let index = 0; + index < contextManager.offscreenRenderTargetContainers.length; + ++index + ) { + const container = contextManager.offscreenRenderTargetContainers[index]; + if (container.inUse == false) { + container.inUse = true; + return container; + } + } + return null; + } + + /** + * 新たにリソースコンテナを作成する。 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * @param width 幅 + * @param height 高さ + * @param previousFramebuffer 前のフレームバッファ + * @return 作成されたリソースコンテナ + */ + public createOffscreenRenderTargetContainer( + gl: WebGLRenderingContext | WebGL2RenderingContext, + width: number, + height: number, + previousFramebuffer: WebGLFramebuffer + ): CubismRenderTargetContainer { + const renderTarget = new CubismRenderTarget_WebGL(); + + if ( + !renderTarget.createRenderTarget(gl, width, height, previousFramebuffer) + ) { + CubismLogError('Failed to create offscreen render texture.'); + return null; + } + + const offscreenRenderTextureContainer = new CubismRenderTargetContainer( + renderTarget.getColorBuffer(), + renderTarget.getRenderTexture(), + true + ); + + const contextManager = this.getContextManager(gl); + contextManager.offscreenRenderTargetContainers.push( + offscreenRenderTextureContainer + ); + + return offscreenRenderTextureContainer; + } + + private static _instance: CubismWebGLOffscreenManager; // オフスクリーン描画用レンダーターゲットマネージャ + private _contextManagers: Map< + WebGLRenderingContext | WebGL2RenderingContext, + CubismWebGLContextManager + >; // WebGLContextごとのマネージャー +} diff --git a/avatar-h5-renderer/framework/src/rendering/cubismoffscreenrendertarget_webgl.ts b/avatar-h5-renderer/framework/src/rendering/cubismoffscreenrendertarget_webgl.ts new file mode 100644 index 0000000..d1efae0 --- /dev/null +++ b/avatar-h5-renderer/framework/src/rendering/cubismoffscreenrendertarget_webgl.ts @@ -0,0 +1,240 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismRenderTarget_WebGL } from './cubismrendertarget_webgl'; +import { CubismWebGLOffscreenManager } from './cubismoffscreenmanager'; +import { CubismLogError } from '../utils/cubismdebug'; + +/** + * WebGL用オフスクリーンサーフェス + * マスクの描画及びオフスクリーン機能に必要なフレームバッファなどを管理する。 + */ +export class CubismOffscreenRenderTarget_WebGL extends CubismRenderTarget_WebGL { + /** + * リソースコンテナマネージャを初期化する。 + * + * @param displayBufferWidth レンダーターゲットの幅 + * @param displayBufferHeight レンダーターゲットの高さ + */ + private initializeOffscreenManager( + gl: WebGLRenderingContext | WebGL2RenderingContext, + displayBufferWidth: number, + displayBufferHeight: number + ): void { + this._gl = gl; + this._webGLOffscreenManager = CubismWebGLOffscreenManager.getInstance(); + if (this._webGLOffscreenManager.getContainerSize(gl) === 0) { + this._webGLOffscreenManager.initialize( + gl, + displayBufferWidth, + displayBufferHeight + ); + } + } + + /** + * オフスクリーン描画用レンダーターゲットをセットする。 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * NOTE: Cubism 5.3以降のモデルが使用される場合はWebGL2RenderingContextを使用すること。 + * @param displayBufferWidth レンダーターゲットの幅 + * @param displayBufferHeight レンダーターゲットの高さ + * @param previousFramebuffer 前のフレームバッファ + */ + public setOffscreenRenderTarget( + gl: WebGLRenderingContext | WebGL2RenderingContext, + displayBufferWidth: number, + displayBufferHeight: number, + previousFramebuffer: WebGLFramebuffer + ): void { + // マネージャがなければ初期化 + if (this._webGLOffscreenManager == null) { + this.initializeOffscreenManager( + gl, + displayBufferWidth, + displayBufferHeight + ); + } + + // 使用可能なリソースコンテナを取得する + const offscreenRenderTargetContainer = + this._webGLOffscreenManager.getOffscreenRenderTargetContainers( + gl, + displayBufferWidth, + displayBufferHeight, + previousFramebuffer + ); + + if (offscreenRenderTargetContainer == null) { + CubismLogError('Failed to acquire offscreen render texture container.'); + return; + } + + this._colorBuffer = offscreenRenderTargetContainer.getColorBuffer(); + this._renderTexture = offscreenRenderTargetContainer.getRenderTexture(); + + this._bufferWidth = displayBufferWidth; + this._bufferHeight = displayBufferHeight; + + this._gl = gl; + + if (this._renderTexture == null) { + this._renderTexture = previousFramebuffer; + CubismLogError('Failed to create offscreen render texture.'); + } + + return; + } + + /** + * リソースコンテナの使用状態を取得 + * + * @return 使用中はtrue、未使用の場合はfalse + */ + public getUsingRenderTextureState(): boolean { + if (this._webGLOffscreenManager == null || this._gl == null) { + return true; + } + + return this._webGLOffscreenManager.getUsingRenderTextureState( + this._gl, + this._renderTexture + ); + } + + /** + * リソースコンテナの使用を開始する。 + */ + public startUsingRenderTexture(): void { + if (this._webGLOffscreenManager == null || this._gl == null) { + return; + } + + this._webGLOffscreenManager.startUsingRenderTexture( + this._gl, + this._renderTexture + ); + } + + /** + * リソースコンテナの使用を終了する。 + */ + public stopUsingRenderTexture(): void { + if (this._webGLOffscreenManager == null || this._gl == null) { + return; + } + + this._webGLOffscreenManager.stopUsingRenderTexture( + this._gl, + this._renderTexture + ); + } + + /** + * オフスクリーンのインデックスを設定する。 + * + * @param offscreenIndex オフスクリーンのインデックス + */ + public setOffscreenIndex(offscreenIndex: number): void { + this._offscreenIndex = offscreenIndex; + } + + /** + * オフスクリーンのインデックスを取得する。 + * + * @return オフスクリーンのインデックス + */ + public getOffscreenIndex(): number { + return this._offscreenIndex; + } + + /** + * 以前のオフスクリーン描画用レンダーターゲットを設定する。 + * + * @param oldOffscreen 以前のオフスクリーン描画用レンダーターゲット + */ + public setOldOffscreen( + oldOffscreen: CubismOffscreenRenderTarget_WebGL + ): void { + this._oldOffscreen = oldOffscreen; + } + + /** + * 以前のオフスクリーン描画用レンダーターゲットを取得する。 + * + * @return 以前のオフスクリーン描画用レンダーターゲット + */ + public getOldOffscreen(): CubismOffscreenRenderTarget_WebGL { + return this._oldOffscreen; + } + + /** + * 親のオフスクリーン描画用レンダーターゲットを設定する。 + * + * @param parentOffscreenRenderTarget 親のオフスクリーン描画用レンダーターゲット + */ + public setParentPartOffscreen( + parentOffscreenRenderTarget: CubismOffscreenRenderTarget_WebGL + ): void { + this._parentOffscreenRenderTarget = parentOffscreenRenderTarget; + } + + /** + * 親のオフスクリーン描画用レンダーターゲットを取得する。 + * + * @return 親のオフスクリーン描画用レンダーターゲット + */ + public getParentPartOffscreen(): CubismOffscreenRenderTarget_WebGL { + return this._parentOffscreenRenderTarget; + } + + /** + * コンストラクタ + */ + constructor() { + super(); + this._offscreenIndex = -1; + this._parentOffscreenRenderTarget = null; + this._oldOffscreen = null; + this._webGLOffscreenManager = null; + } + + public release(): void { + if ( + this._webGLOffscreenManager != null && + this._gl != null && + this._renderTexture != null + ) { + this._webGLOffscreenManager.stopUsingRenderTexture( + this._gl, + this._renderTexture + ); + } + + if (this._colorBuffer && this._gl) { + this._gl.deleteTexture(this._colorBuffer); + this._colorBuffer = null; + } + if (this._renderTexture && this._gl) { + this._gl.deleteFramebuffer(this._renderTexture); + this._renderTexture = null; + } + + if (this._webGLOffscreenManager != null) { + this._webGLOffscreenManager = null; + } + + this._oldOffscreen = null; + this._parentOffscreenRenderTarget = null; + } + + private _offscreenIndex: number; // オフスクリーンのインデックス + private _parentOffscreenRenderTarget: CubismOffscreenRenderTarget_WebGL; // 親のオフスクリーン描画用レンダーターゲット + private _oldOffscreen: CubismOffscreenRenderTarget_WebGL; // 以前のオフスクリーン描画用レンダーターゲット + private _webGLOffscreenManager: CubismWebGLOffscreenManager; // オフスクリーン描画用レンダーターゲットマネージャ + protected _gl: WebGLRenderingContext | WebGL2RenderingContext; // WebGLコンテキスト +} diff --git a/avatar-h5-renderer/framework/src/rendering/cubismrenderer.ts b/avatar-h5-renderer/framework/src/rendering/cubismrenderer.ts new file mode 100644 index 0000000..246f0a9 --- /dev/null +++ b/avatar-h5-renderer/framework/src/rendering/cubismrenderer.ts @@ -0,0 +1,425 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from '../math/cubismmath'; +import { CubismMatrix44 } from '../math/cubismmatrix44'; +import { CubismModel } from '../model/cubismmodel'; +import { csmRect } from '../type/csmrectf'; +import { ICubismClippingManager } from './cubismclippingmanager'; +import { CubismLogInfo } from '../utils/cubismdebug'; + +/** + * モデル描画を処理するレンダラ + * + * サブクラスに環境依存の描画命令を記述する。 + */ +export abstract class CubismRenderer { + /** + * レンダラのインスタンスを生成して取得する + * + * @return レンダラのインスタンス + */ + public static create(): CubismRenderer { + return null; + } + + /** + * レンダラのインスタンスを解放する + */ + public static delete(renderer: CubismRenderer): void { + renderer = null; + } + + /** + * レンダラの初期化処理を実行する + * 引数に渡したモデルからレンダラの初期化処理に必要な情報を取り出すことができる + * + * @param model モデルのインスタンス + */ + public initialize(model: CubismModel): void { + this._model = model; + + // ブレンドモード使用時は必ず高精細にする + if (model.isBlendModeEnabled()) { + this.useHighPrecisionMask(true); + CubismLogInfo( + 'This model uses a high-resolution mask because it operates in blend mode.' + ); + } + } + + /** + * モデルを描画する + * @param shaderPath ブレンドモード用シェーダのパス + */ + public drawModel(shaderPath: string = null): void { + if (this.getModel() == null) return; + + // NOTE: WebGL最適化のため、デフォルトではコメントアウト + //this.saveProfile(); + + this.doDrawModel(shaderPath); + + // NOTE: WebGL最適化のため、デフォルトではコメントアウト + //this.restoreProfile(); + } + + /** + * Model-View-Projection 行列をセットする + * 配列は複製されるので、元の配列は外で破棄して良い + * + * @param matrix44 Model-View-Projection 行列 + */ + public setMvpMatrix(matrix44: CubismMatrix44): void { + this._mvpMatrix4x4.setMatrix(matrix44.getArray()); + } + + /** + * Model-View-Projection 行列を取得する + * + * @return Model-View-Projection 行列 + */ + public getMvpMatrix(): CubismMatrix44 { + return this._mvpMatrix4x4; + } + + /** + * モデルの色をセットする + * 各色0.0~1.0の間で指定する(1.0が標準の状態) + * + * @param red 赤チャンネルの値 + * @param green 緑チャンネルの値 + * @param blue 青チャンネルの値 + * @param alpha αチャンネルの値 + */ + public setModelColor( + red: number, + green: number, + blue: number, + alpha: number + ): void { + this._modelColor.r = CubismMath.clamp(red, 0.0, 1.0); + this._modelColor.g = CubismMath.clamp(green, 0.0, 1.0); + this._modelColor.b = CubismMath.clamp(blue, 0.0, 1.0); + this._modelColor.a = CubismMath.clamp(alpha, 0.0, 1.0); + } + + /** + * モデルの色を取得する + * 各色0.0~1.0の間で指定する(1.0が標準の状態) + * + * @return RGBAのカラー情報 + */ + public getModelColor(): CubismTextureColor { + return JSON.parse(JSON.stringify(this._modelColor)); + } + + /** + * 透明度を考慮したモデルの色を計算する。 + * + * @param opacity 透明度 + * + * @return RGBAのカラー情報 + */ + getModelColorWithOpacity(opacity: number): CubismTextureColor { + const modelColorRGBA: CubismTextureColor = this.getModelColor(); + modelColorRGBA.a *= opacity; + if (this.isPremultipliedAlpha()) { + modelColorRGBA.r *= modelColorRGBA.a; + modelColorRGBA.g *= modelColorRGBA.a; + modelColorRGBA.b *= modelColorRGBA.a; + } + return modelColorRGBA; + } + + /** + * 乗算済みαの有効・無効をセットする + * 有効にするならtrue、無効にするならfalseをセットする + */ + public setIsPremultipliedAlpha(enable: boolean): void { + this._isPremultipliedAlpha = enable; + } + + /** + * 乗算済みαの有効・無効を取得する + * @return true 乗算済みのα有効 + * false 乗算済みのα無効 + */ + public isPremultipliedAlpha(): boolean { + return this._isPremultipliedAlpha; + } + + /** + * カリング(片面描画)の有効・無効をセットする。 + * 有効にするならtrue、無効にするならfalseをセットする + */ + public setIsCulling(culling: boolean): void { + this._isCulling = culling; + } + + /** + * カリング(片面描画)の有効・無効を取得する。 + * + * @return true カリング有効 + * false カリング無効 + */ + public isCulling(): boolean { + return this._isCulling; + } + + /** + * テクスチャの異方性フィルタリングのパラメータをセットする + * パラメータ値の影響度はレンダラの実装に依存する + * + * @param n パラメータの値 + */ + public setAnisotropy(n: number): void { + this._anisotropy = n; + } + + /** + * テクスチャの異方性フィルタリングのパラメータをセットする + * + * @return 異方性フィルタリングのパラメータ + */ + public getAnisotropy(): number { + return this._anisotropy; + } + + /** + * レンダリングするモデルを取得する + * + * @return レンダリングするモデル + */ + public getModel(): CubismModel { + return this._model; + } + + /** + * マスク描画の方式を変更する。 + * falseの場合、マスクを1枚のテクスチャに分割してレンダリングする(デフォルト) + * 高速だが、マスク個数の上限が36に限定され、質も荒くなる + * trueの場合、パーツ描画の前にその都度必要なマスクを描き直す + * レンダリング品質は高いが描画処理負荷は増す + * + * @param high 高精細マスクに切り替えるか? + */ + public useHighPrecisionMask(high: boolean): void { + this._useHighPrecisionMask = high; + } + + /** + * マスクの描画方式を取得する + * + * @return true 高精細方式 + * false デフォルト + */ + public isUsingHighPrecisionMask(): boolean { + return this._useHighPrecisionMask; + } + + /** + * モデルを描画したバッファのサイズを設定 + * + * @param[in] width -> モデルを描画したバッファの幅 + * @param[in] height -> モデルを描画したバッファの高さ + */ + public setRenderTargetSize(width: number, height: number): void { + this._modelRenderTargetWidth = width; + this._modelRenderTargetHeight = height; + } + + /** + * コンストラクタ + */ + protected constructor(width: number, height: number) { + this._modelRenderTargetWidth = width; + this._modelRenderTargetHeight = height; + this._isCulling = false; + this._isPremultipliedAlpha = false; + this._anisotropy = 0.0; + this._model = null; + this._modelColor = new CubismTextureColor(); + this._useHighPrecisionMask = false; + + // 単位行列に初期化 + this._mvpMatrix4x4 = new CubismMatrix44(); + this._mvpMatrix4x4.loadIdentity(); + } + + /** + * モデル描画直前のオフスクリーン設定を行う + */ + public abstract beforeDrawModelRenderTarget(): void; + + /** + * モデル描画直後のオフスクリーン設定を行う + */ + public abstract afterDrawModelRenderTarget(): void; + + /** + * モデル描画の実装 + * @param shaderPath ブレンドモード用シェーダのパス + */ + public abstract doDrawModel(shaderPath: string): void; + + /** + * モデル描画直前のレンダラのステートを保持する + */ + protected abstract saveProfile(): void; + + /** + * モデル描画直前のレンダラのステートを復帰する + */ + protected abstract restoreProfile(): void; + + /** + * レンダラが保持する静的なリソースを開放する + */ + public static staticRelease: any; + + protected _mvpMatrix4x4: CubismMatrix44; // Model-View-Projection 行列 + protected _modelColor: CubismTextureColor; // モデル自体のカラー(RGBA) + protected _isCulling: boolean; // カリングが有効ならtrue + protected _isPremultipliedAlpha: boolean; // 乗算済みαならtrue + protected _anisotropy: any; // テクスチャの異方性フィルタリングのパラメータ + protected _model: CubismModel; // レンダリング対象のモデル + protected _useHighPrecisionMask: boolean; // falseの場合、マスクを纏めて描画する trueの場合、マスクはパーツ描画ごとに書き直す + + protected _modelRenderTargetWidth: number; + protected _modelRenderTargetHeight: number; +} + +export enum CubismBlendMode { + CubismBlendMode_Normal = 0, // 通常 + CubismBlendMode_Additive = 1, // 加算 + CubismBlendMode_Multiplicative = 2 // 乗算 +} + +/** + * オブジェクトのタイプ + */ +export enum DrawableObjectType { + DrawableObjectType_Drawable = 0, + DrawableObjectType_Offscreen = 1 +} + +/** + * テクスチャの色をRGBAで扱うためのクラス + */ +export class CubismTextureColor { + /** + * コンストラクタ + */ + constructor(r = 1.0, g = 1.0, b = 1.0, a = 1.0) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + r: number; // 赤チャンネル + g: number; // 緑チャンネル + b: number; // 青チャンネル + a: number; // αチャンネル +} + +/** + * クリッピングマスクのコンテキスト + */ +export abstract class CubismClippingContext { + /** + * 引数付きコンストラクタ + */ + public constructor(clippingDrawableIndices: Int32Array, clipCount: number) { + // クリップしている(=マスク用の)Drawableのインデックスリスト + this._clippingIdList = clippingDrawableIndices; + + // マスクの数 + this._clippingIdCount = clipCount; + + this._allClippedDrawRect = new csmRect(); + this._layoutBounds = new csmRect(); + + this._clippedDrawableIndexList = []; + this._clippedOffscreenIndexList = []; + + this._matrixForMask = new CubismMatrix44(); + this._matrixForDraw = new CubismMatrix44(); + + this._bufferIndex = 0; + this._layoutChannelIndex = 0; + } + + /** + * このマスクを管理するマネージャのインスタンスを取得する + * @return クリッピングマネージャのインスタンス + */ + public abstract getClippingManager(): ICubismClippingManager; + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._layoutBounds != null) { + this._layoutBounds = null; + } + + if (this._allClippedDrawRect != null) { + this._allClippedDrawRect = null; + } + + if (this._clippedDrawableIndexList != null) { + this._clippedDrawableIndexList = null; + } + if (this._clippedOffscreenIndexList != null) { + this._clippedOffscreenIndexList = null; + } + } + + /** + * このマスクにクリップされる描画オブジェクトを追加する + * + * @param drawableIndex クリッピング対象に追加する描画オブジェクトのインデックス + */ + public addClippedDrawable(drawableIndex: number) { + this._clippedDrawableIndexList.push(drawableIndex); + } + + /** + * このマスクにクリップされるオフスクリーンオブジェクトを追加する + * + * @param offscreenIndex クリッピング対象に追加するオフスクリーンオブジェクトのインデックス + */ + public addClippedOffscreen(offscreenIndex: number) { + this._clippedOffscreenIndexList.push(offscreenIndex); + } + + public _isUsing: boolean; // 現在の描画状態でマスクの準備が必要ならtrue + public readonly _clippingIdList: Int32Array; // クリッピングマスクのIDリスト + public _clippingIdCount: number; // クリッピングマスクの数 + public _layoutChannelIndex: number; // RGBAのいずれのチャンネルにこのクリップを配置するか(0:R, 1:G, 2:B, 3:A) + public _layoutBounds: csmRect; // マスク用チャンネルのどの領域にマスクを入れるか(View座標-1~1, UVは0~1に直す) + public _allClippedDrawRect: csmRect; // このクリッピングで、クリッピングされるすべての描画オブジェクトの囲み矩形(毎回更新) + public _matrixForMask: CubismMatrix44; // マスクの位置計算結果を保持する行列 + public _matrixForDraw: CubismMatrix44; // 描画オブジェクトの位置計算結果を保持する行列 + public _clippedDrawableIndexList: number[]; // このマスクにクリップされる描画オブジェクトのリスト + public _clippedOffscreenIndexList: number[]; // このマスクにクリップされるオフスクリーンオブジェクトのリスト + public _bufferIndex: number; // このマスクが割り当てられるレンダーテクスチャ(フレームバッファ)やカラーバッファのインデックス +} + +// Namespace definition for compatibility. +import * as $ from './cubismrenderer'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismBlendMode = $.CubismBlendMode; + export type CubismBlendMode = $.CubismBlendMode; + export const CubismRenderer = $.CubismRenderer; + export type CubismRenderer = $.CubismRenderer; + export const CubismTextureColor = $.CubismTextureColor; + export type CubismTextureColor = $.CubismTextureColor; +} diff --git a/avatar-h5-renderer/framework/src/rendering/cubismrenderer_webgl.ts b/avatar-h5-renderer/framework/src/rendering/cubismrenderer_webgl.ts new file mode 100644 index 0000000..9520878 --- /dev/null +++ b/avatar-h5-renderer/framework/src/rendering/cubismrenderer_webgl.ts @@ -0,0 +1,1780 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from '../math/cubismmath'; +import { CubismModel, NoParentIndex } from '../model/cubismmodel'; +import { csmRect } from '../type/csmrectf'; +import { CubismLogError } from '../utils/cubismdebug'; +import { updateSize } from '../utils/cubismarrayutils'; +import { CubismClippingManager } from './cubismclippingmanager'; +import { + CubismClippingContext, + CubismRenderer, + DrawableObjectType +} from './cubismrenderer'; +import { CubismShaderManager_WebGL } from './cubismshader_webgl'; + +const s_invalidValue = -1; // 無効な値を表す定数 +/* + * シェーダをコピーする際に衣装する頂点のインデックス + */ +const s_renderTargetIndexArray: Uint16Array = new Uint16Array([ + 0, 1, 2, 2, 1, 3 +]); + +/** + * クリッピングマスクの処理を実行するクラス + */ +export class CubismClippingManager_WebGL extends CubismClippingManager { + /** + * WebGLレンダリングコンテキストを設定する + * + * @param gl WebGLレンダリングコンテキスト + */ + public setGL(gl: WebGLRenderingContext): void { + this.gl = gl; + } + + /** + * コンストラクタ + */ + public constructor() { + super(CubismClippingContext_WebGL); + } + + /** + * クリッピングコンテキストを作成する。モデル描画時に実行する。 + * + * @param model モデルのインスタンス + * @param renderer レンダラのインスタンス + * @param lastFbo フレームバッファ + * @param lastViewport ビューポート + * @param drawObjectType 描画オブジェクトのタイプ + */ + public setupClippingContext( + model: CubismModel, + renderer: CubismRenderer_WebGL, + lastFbo: WebGLFramebuffer, + lastViewport: number[], + drawObjectType: DrawableObjectType + ): void { + // 全てのクリッピングを用意する + // 同じクリップ(複数の場合はまとめて一つのクリップ)を使う場合は1度だけ設定する + let usingClipCount = 0; + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.length; + clipIndex++ + ) { + // 1つのクリッピングマスクに関して + const cc: CubismClippingContext_WebGL = + this._clippingContextListForMask[clipIndex]; + + // このクリップを利用する描画オブジェクト群全体を囲む矩形を計算 + switch (drawObjectType) { + case DrawableObjectType.DrawableObjectType_Drawable: + default: + this.calcClippedDrawableTotalBounds(model, cc); + break; + case DrawableObjectType.DrawableObjectType_Offscreen: + this.calcClippedOffscreenTotalBounds(model, cc); + break; + } + + if (cc._isUsing) { + usingClipCount++; // 使用中としてカウント + } + } + + if (usingClipCount <= 0) { + return; + } + + // 生成したFrameBufferと同じサイズでビューポートを設定 + this.gl.viewport( + 0, + 0, + this._clippingMaskBufferSize, + this._clippingMaskBufferSize + ); + + // 後の計算のためにインデックスの最初をセット + switch (drawObjectType) { + case DrawableObjectType.DrawableObjectType_Drawable: + default: + this._currentMaskBuffer = renderer.getDrawableMaskBuffer(0); + break; + case DrawableObjectType.DrawableObjectType_Offscreen: + this._currentMaskBuffer = renderer.getOffscreenMaskBuffer(0); + break; + } + + // ---------- マスク描画処理 ---------- + this._currentMaskBuffer.beginDraw(lastFbo); + + renderer.preDraw(); // バッファをクリアする + + this.setupLayoutBounds(usingClipCount); + + // サイズがレンダーテクスチャの枚数と合わない場合は合わせる + if (this._clearedMaskBufferFlags.length != this._renderTextureCount) { + this._clearedMaskBufferFlags.length = 0; + this._clearedMaskBufferFlags = new Array( + this._renderTextureCount + ); + for (let i = 0; i < this._clearedMaskBufferFlags.length; i++) { + this._clearedMaskBufferFlags[i] = false; + } + } + + // マスクのクリアフラグを毎フレーム開始時に初期化 + for (let index = 0; index < this._clearedMaskBufferFlags.length; index++) { + this._clearedMaskBufferFlags[index] = false; + } + + // 実際にマスクを生成する + // 全てのマスクをどのようにレイアウトして描くかを決定し、ClipContext, ClippedDrawContextに記憶する + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.length; + clipIndex++ + ) { + // --- 実際に1つのマスクを描く --- + const clipContext: CubismClippingContext_WebGL = + this._clippingContextListForMask[clipIndex]; + const allClipedDrawRect: csmRect = clipContext._allClippedDrawRect; // このマスクを使う、すべての描画オブジェクトの論理座標上の囲み矩形 + const layoutBoundsOnTex01: csmRect = clipContext._layoutBounds; // この中にマスクを収める + const margin = 0.05; // モデル座標上の矩形を、適宜マージンを付けて使う + let scaleX = 0; + let scaleY = 0; + + // clipContextに設定したレンダーテクスチャをインデックスで取得 + let maskBuffer: CubismRenderTarget_WebGL; + switch (drawObjectType) { + case DrawableObjectType.DrawableObjectType_Drawable: + default: + maskBuffer = renderer.getDrawableMaskBuffer(clipContext._bufferIndex); + break; + case DrawableObjectType.DrawableObjectType_Offscreen: + maskBuffer = renderer.getOffscreenMaskBuffer( + clipContext._bufferIndex + ); + break; + } + + // 現在のレンダーテクスチャがclipContextのものと異なる場合 + if (this._currentMaskBuffer != maskBuffer) { + this._currentMaskBuffer.endDraw(); // 前のレンダーテクスチャの描画を終了 + this._currentMaskBuffer = maskBuffer; + this._currentMaskBuffer.beginDraw(lastFbo); // 新しいレンダーテクスチャの描画を開始 + + renderer.preDraw(); // バッファをクリアする + } + + this._tmpBoundsOnModel.setRect(allClipedDrawRect); + this._tmpBoundsOnModel.expand( + allClipedDrawRect.width * margin, + allClipedDrawRect.height * margin + ); + //########## 本来は割り当てられた領域の全体を使わず必要最低限のサイズがよい + + // シェーダ用の計算式を求める。回転を考慮しない場合は以下のとおり + // movePeriod' = movePeriod * scaleX + offX [[ movePeriod' = (movePeriod - tmpBoundsOnModel.movePeriod)*scale + layoutBoundsOnTex01.movePeriod ]] + scaleX = layoutBoundsOnTex01.width / this._tmpBoundsOnModel.width; + scaleY = layoutBoundsOnTex01.height / this._tmpBoundsOnModel.height; + + //--------- draw時の mask 参照用行列を計算--------- + this.createMatrixForMask(false, layoutBoundsOnTex01, scaleX, scaleY); + + clipContext._matrixForMask.setMatrix(this._tmpMatrixForMask.getArray()); + clipContext._matrixForDraw.setMatrix(this._tmpMatrixForDraw.getArray()); + + if (drawObjectType == DrawableObjectType.DrawableObjectType_Offscreen) { + // clipContext * mvp^-1 + const invertMvp = renderer.getMvpMatrix().getInvert(); + clipContext._matrixForDraw.multiplyByMatrix(invertMvp); + } + + const clipDrawCount: number = clipContext._clippingIdCount; + for (let i = 0; i < clipDrawCount; i++) { + const clipDrawIndex: number = clipContext._clippingIdList[i]; + + // 頂点情報が更新されておらず、信頼性がない場合は描画をパスする + if ( + !model.getDrawableDynamicFlagVertexPositionsDidChange(clipDrawIndex) + ) { + continue; + } + + renderer.setIsCulling(model.getDrawableCulling(clipDrawIndex) != false); + + // マスクがクリアされていないなら処理する + if (!this._clearedMaskBufferFlags[clipContext._bufferIndex]) { + // マスクをクリアする + // (仮仕様) 1が無効(描かれない)領域、0が有効(描かれる)領域。(シェーダーCd*Csで0に近い値をかけてマスクを作る。1をかけると何も起こらない) + this.gl.clearColor(1.0, 1.0, 1.0, 1.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + this._clearedMaskBufferFlags[clipContext._bufferIndex] = true; + } + + // 今回専用の変換を適用して描く + // チャンネルも切り替える必要がある(A,R,G,B) + renderer.setClippingContextBufferForMask(clipContext); + + renderer.drawMeshWebGL(model, clipDrawIndex); + } + } + + // --- 後処理 --- + this._currentMaskBuffer.endDraw(); // マスクの描画を終了 + renderer.setClippingContextBufferForMask(null); + + this.gl.viewport( + lastViewport[0], + lastViewport[1], + lastViewport[2], + lastViewport[3] + ); + } + + /** + * マスクの合計数をカウント + * + * @return マスクの合計数を返す + */ + public getClippingMaskCount(): number { + return this._clippingContextListForMask.length; + } + + _currentMaskBuffer: CubismRenderTarget_WebGL; // マスク用オフスクリーンサーフェス + + gl: WebGLRenderingContext; // WebGLレンダリングコンテキスト +} + +/** + * クリッピングマスクのコンテキスト + */ +export class CubismClippingContext_WebGL extends CubismClippingContext { + /** + * 引数付きコンストラクタ + * + * @param manager マスクを管理しているマネージャのインスタンス + * @param clippingDrawableIndices クリップしているDrawableのインデックスリスト + * @param clipCount クリップしているDrawableの個数 + */ + public constructor( + manager: CubismClippingManager_WebGL, + clippingDrawableIndices: Int32Array, + clipCount: number + ) { + super(clippingDrawableIndices, clipCount); + this._owner = manager; + } + + /** + * このマスクを管理するマネージャのインスタンスを取得する + * + * @return クリッピングマネージャのインスタンス + */ + public getClippingManager(): CubismClippingManager_WebGL { + return this._owner; + } + + /** + * WebGLレンダリングコンテキストを設定する + * + * @param gl WebGLレンダリングコンテキスト + */ + public setGl(gl: WebGLRenderingContext): void { + this._owner.setGL(gl); + } + + private _owner: CubismClippingManager_WebGL; // このマスクを管理しているマネージャのインスタンス +} + +/** + * Cubismモデルを描画する直前のWebGLのステートを保持・復帰させるクラス + */ +export class CubismRendererProfile_WebGL { + /** + * WebGLの有効・無効をセットする + * + * @param index 有効・無効にする機能 + * @param enabled trueなら有効にする + */ + private setGlEnable(index: GLenum, enabled: GLboolean): void { + if (enabled) this.gl.enable(index); + else this.gl.disable(index); + } + + /** + * WebGLのVertex Attribute Array機能の有効・無効をセットする + * + * @param index 有効・無効にする機能 + * @param enabled trueなら有効にする + */ + private setGlEnableVertexAttribArray( + index: GLuint, + enabled: GLboolean + ): void { + if (enabled) this.gl.enableVertexAttribArray(index); + else this.gl.disableVertexAttribArray(index); + } + + /** + * WebGLのステートを保持する + */ + public save(): void { + if (this.gl == null) { + CubismLogError( + "'gl' is null. WebGLRenderingContext is required.\nPlease call 'CubimRenderer_WebGL.startUp' function." + ); + return; + } + //-- push state -- + this._lastArrayBufferBinding = this.gl.getParameter( + this.gl.ARRAY_BUFFER_BINDING + ); + this._lastElementArrayBufferBinding = this.gl.getParameter( + this.gl.ELEMENT_ARRAY_BUFFER_BINDING + ); + this._lastProgram = this.gl.getParameter(this.gl.CURRENT_PROGRAM); + + this._lastActiveTexture = this.gl.getParameter(this.gl.ACTIVE_TEXTURE); + this.gl.activeTexture(this.gl.TEXTURE1); //テクスチャユニット1をアクティブに(以後の設定対象とする) + this._lastTexture1Binding2D = this.gl.getParameter( + this.gl.TEXTURE_BINDING_2D + ); + + this.gl.activeTexture(this.gl.TEXTURE0); //テクスチャユニット0をアクティブに(以後の設定対象とする) + this._lastTexture0Binding2D = this.gl.getParameter( + this.gl.TEXTURE_BINDING_2D + ); + + this._lastVertexAttribArrayEnabled[0] = this.gl.getVertexAttrib( + 0, + this.gl.VERTEX_ATTRIB_ARRAY_ENABLED + ); + this._lastVertexAttribArrayEnabled[1] = this.gl.getVertexAttrib( + 1, + this.gl.VERTEX_ATTRIB_ARRAY_ENABLED + ); + this._lastVertexAttribArrayEnabled[2] = this.gl.getVertexAttrib( + 2, + this.gl.VERTEX_ATTRIB_ARRAY_ENABLED + ); + this._lastVertexAttribArrayEnabled[3] = this.gl.getVertexAttrib( + 3, + this.gl.VERTEX_ATTRIB_ARRAY_ENABLED + ); + + this._lastScissorTest = this.gl.isEnabled(this.gl.SCISSOR_TEST); + this._lastStencilTest = this.gl.isEnabled(this.gl.STENCIL_TEST); + this._lastDepthTest = this.gl.isEnabled(this.gl.DEPTH_TEST); + this._lastCullFace = this.gl.isEnabled(this.gl.CULL_FACE); + this._lastBlend = this.gl.isEnabled(this.gl.BLEND); + + this._lastFrontFace = this.gl.getParameter(this.gl.FRONT_FACE); + + this._lastColorMask = this.gl.getParameter(this.gl.COLOR_WRITEMASK); + + // backup blending + this._lastBlending[0] = this.gl.getParameter(this.gl.BLEND_SRC_RGB); + this._lastBlending[1] = this.gl.getParameter(this.gl.BLEND_DST_RGB); + this._lastBlending[2] = this.gl.getParameter(this.gl.BLEND_SRC_ALPHA); + this._lastBlending[3] = this.gl.getParameter(this.gl.BLEND_DST_ALPHA); + } + + /** + * 保持したWebGLのステートを復帰させる + */ + public restore(): void { + if (this.gl == null) { + CubismLogError( + "'gl' is null. WebGLRenderingContext is required.\nPlease call 'CubimRenderer_WebGL.startUp' function." + ); + return; + } + this.gl.useProgram(this._lastProgram); + + this.setGlEnableVertexAttribArray(0, this._lastVertexAttribArrayEnabled[0]); + this.setGlEnableVertexAttribArray(1, this._lastVertexAttribArrayEnabled[1]); + this.setGlEnableVertexAttribArray(2, this._lastVertexAttribArrayEnabled[2]); + this.setGlEnableVertexAttribArray(3, this._lastVertexAttribArrayEnabled[3]); + + this.setGlEnable(this.gl.SCISSOR_TEST, this._lastScissorTest); + this.setGlEnable(this.gl.STENCIL_TEST, this._lastStencilTest); + this.setGlEnable(this.gl.DEPTH_TEST, this._lastDepthTest); + this.setGlEnable(this.gl.CULL_FACE, this._lastCullFace); + this.setGlEnable(this.gl.BLEND, this._lastBlend); + + this.gl.frontFace(this._lastFrontFace); + + this.gl.colorMask( + this._lastColorMask[0], + this._lastColorMask[1], + this._lastColorMask[2], + this._lastColorMask[3] + ); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this._lastArrayBufferBinding); //前にバッファがバインドされていたら破棄する必要がある + this.gl.bindBuffer( + this.gl.ELEMENT_ARRAY_BUFFER, + this._lastElementArrayBufferBinding + ); + + this.gl.activeTexture(this.gl.TEXTURE1); //テクスチャユニット1を復元 + this.gl.bindTexture(this.gl.TEXTURE_2D, this._lastTexture1Binding2D); + + this.gl.activeTexture(this.gl.TEXTURE0); //テクスチャユニット0を復元 + this.gl.bindTexture(this.gl.TEXTURE_2D, this._lastTexture0Binding2D); + + this.gl.activeTexture(this._lastActiveTexture); + + this.gl.blendFuncSeparate( + this._lastBlending[0], + this._lastBlending[1], + this._lastBlending[2], + this._lastBlending[3] + ); + } + + /** + * WebGLレンダリングコンテキストを設定する + * + * @param gl WebGLレンダリングコンテキスト + */ + public setGl(gl: WebGLRenderingContext): void { + this.gl = gl; + } + + /** + * コンストラクタ + */ + constructor() { + this._lastVertexAttribArrayEnabled = new Array(4); + this._lastColorMask = new Array(4); + this._lastBlending = new Array(4); + } + + private _lastArrayBufferBinding: GLint; ///< モデル描画直前の頂点バッファ + private _lastElementArrayBufferBinding: GLint; ///< モデル描画直前のElementバッファ + private _lastProgram: GLint; ///< モデル描画直前のシェーダプログラムバッファ + private _lastActiveTexture: GLint; ///< モデル描画直前のアクティブなテクスチャ + private _lastTexture0Binding2D: GLint; ///< モデル描画直前のテクスチャユニット0 + private _lastTexture1Binding2D: GLint; ///< モデル描画直前のテクスチャユニット1 + private _lastVertexAttribArrayEnabled: GLboolean[]; ///< モデル描画直前のテクスチャユニット1 + private _lastScissorTest: GLboolean; ///< モデル描画直前のGL_VERTEX_ATTRIB_ARRAY_ENABLEDパラメータ + private _lastBlend: GLboolean; ///< モデル描画直前のGL_SCISSOR_TESTパラメータ + private _lastStencilTest: GLboolean; ///< モデル描画直前のGL_STENCIL_TESTパラメータ + private _lastDepthTest: GLboolean; ///< モデル描画直前のGL_DEPTH_TESTパラメータ + private _lastCullFace: GLboolean; ///< モデル描画直前のGL_CULL_FACEパラメータ + private _lastFrontFace: GLint; ///< モデル描画直前のGL_CULL_FACEパラメータ + private _lastColorMask: GLboolean[]; ///< モデル描画直前のGL_COLOR_WRITEMASKパラメータ + private _lastBlending: GLint[]; ///< モデル描画直前のカラーブレンディングパラメータ + + gl: WebGLRenderingContext; +} + +/** + * WebGL用の描画命令を実装したクラス + */ +export class CubismRenderer_WebGL extends CubismRenderer { + /** + * レンダラの初期化処理を実行する + * 引数に渡したモデルからレンダラの初期化処理に必要な情報を取り出すことができる + * NOTE: WebGLコンテキストが初期化されていない可能性があるため、ここではWebGLコンテキストを使う初期化は行わない。 + * + * @param model モデルのインスタンス + * @param maskBufferCount バッファの生成数 + */ + public initialize(model: CubismModel, maskBufferCount = 1): void { + if (model.isUsingMasking()) { + this._drawableClippingManager = new CubismClippingManager_WebGL(); // クリッピングマスク・バッファ前処理方式を初期化 + this._drawableClippingManager.initializeForDrawable( + model, + maskBufferCount + ); + } + + if (model.isUsingMaskingForOffscreen()) { + this._offscreenClippingManager = new CubismClippingManager_WebGL(); //クリッピングマスク・バッファ前処理方式を初期化 + this._offscreenClippingManager.initializeForOffscreen( + model, + maskBufferCount + ); + } + + // IndexList と TypeListのサイズをモデルの描画オブジェクト数に合わせる + updateSize( + this._sortedObjectsIndexList, + model.getDrawableCount() + + (model.getOffscreenCount ? model.getOffscreenCount() : 0), + 0, + true + ); + updateSize( + this._sortedObjectsTypeList, + model.getDrawableCount() + + (model.getOffscreenCount ? model.getOffscreenCount() : 0), + 0, + true + ); + + super.initialize(model); // 親クラスの処理を呼ぶ + } + + /** + * オフスクリーンの親を探して設定する + * + * @param model モデルのインスタンス + * @param offscreenCount オフスクリーンの数 + */ + private setupParentOffscreens( + model: CubismModel, + offscreenCount: number + ): void { + let parentOffscreen: CubismOffscreenRenderTarget_WebGL | null; + for ( + let offscreenIndex = 0; + offscreenIndex < offscreenCount; + ++offscreenIndex + ) { + parentOffscreen = null; + const ownerIndex = model.getOffscreenOwnerIndices()[offscreenIndex]; + let parentIndex = model.getPartParentPartIndices()[ownerIndex]; + + // 親のオフスクリーンを探す + while (parentIndex != NoParentIndex) { + for (let i = 0; i < offscreenCount; ++i) { + const ownerIndex = + model.getOffscreenOwnerIndices()[ + this._offscreenList[i].getOffscreenIndex() + ]; + if (ownerIndex != parentIndex) { + continue; //オフスクリーンの所有者がパーツではない場合はスキップ + } + + parentOffscreen = this._offscreenList[i]; + break; + } + + if (parentOffscreen != null) { + break; // 親のオフスクリーンが見つかった場合はループを抜ける + } + + parentIndex = model.getPartParentPartIndices()[parentIndex]; + } + + // 親のオフスクリーンを設定 + this._offscreenList[offscreenIndex].setParentPartOffscreen( + parentOffscreen + ); + } + } + + /** + * WebGLテクスチャのバインド処理 + * CubismRendererにテクスチャを設定し、CubismRenderer内でその画像を参照するためのIndex値を戻り値とする + * + * @param modelTextureNo セットするモデルテクスチャの番号 + * @param glTextureNo WebGLテクスチャの番号 + */ + public bindTexture(modelTextureNo: number, glTexture: WebGLTexture): void { + this._textures.set(modelTextureNo, glTexture); + } + + /** + * WebGLにバインドされたテクスチャのリストを取得する + * + * @return テクスチャのリスト + */ + public getBindedTextures(): Map { + return this._textures; + } + + /** + * クリッピングマスクバッファのサイズを設定する + * マスク用のFrameBufferを破棄、再作成する為処理コストは高い + * + * @param size クリッピングマスクバッファのサイズ + */ + public setClippingMaskBufferSize(size: number) { + // クリッピングマスクを利用しない場合は早期リターン + if (!this._model.isUsingMasking()) { + return; + } + + // インスタンス破棄前にレンダーテクスチャの数を保存 + const renderTextureCount: number = + this._drawableClippingManager.getRenderTextureCount(); + + // FrameBufferのサイズを変更するためにインスタンスを破棄・再作成する + this._drawableClippingManager.release(); + this._drawableClippingManager = void 0; + this._drawableClippingManager = null; + + this._drawableClippingManager = new CubismClippingManager_WebGL(); + + this._drawableClippingManager.setClippingMaskBufferSize(size); + + this._drawableClippingManager.initializeForDrawable( + this.getModel(), + renderTextureCount // インスタンス破棄前に保存したレンダーテクスチャの数 + ); + } + + /** + * クリッピングマスクバッファのサイズを取得する + * + * @return クリッピングマスクバッファのサイズ + */ + public getClippingMaskBufferSize(): number { + return this._model.isUsingMasking() + ? this._drawableClippingManager.getClippingMaskBufferSize() + : s_invalidValue; + } + + /** + * ブレンドモード用のフレームバッファを取得する + * + * @return ブレンドモード用のフレームバッファ + */ + public getModelRenderTarget(index: number): CubismRenderTarget_WebGL { + return this._modelRenderTargets[index]; + } + + /** + * レンダーテクスチャの枚数を取得する + * @return レンダーテクスチャの枚数 + */ + public getRenderTextureCount(): number { + return this._model.isUsingMasking() + ? this._drawableClippingManager.getRenderTextureCount() + : s_invalidValue; + } + + /** + * コンストラクタ + */ + public constructor(width: number, height: number) { + super(width, height); + this._clippingContextBufferForMask = null; + this._clippingContextBufferForDraw = null; + this._rendererProfile = new CubismRendererProfile_WebGL(); + this._textures = new Map(); + this._sortedObjectsIndexList = new Array(); + this._sortedObjectsTypeList = new Array(); + this._bufferData = { + vertex: (WebGLBuffer = null), + uv: (WebGLBuffer = null), + index: (WebGLBuffer = null) + }; + this._modelRenderTargets = new Array(); + this._drawableMasks = new Array(); + this._currentFbo = null; + this._drawableClippingManager = null; + this._offscreenClippingManager = null; + this._offscreenMasks = new Array(); + this._offscreenList = new Array(); + + // テクスチャ対応マップの容量を確保しておく + // this._textures.prepareCapacity(32, true); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._drawableClippingManager) { + this._drawableClippingManager.release(); + this._drawableClippingManager = void 0; + this._drawableClippingManager = null; + } + + if (this.gl == null) { + return; + } + this.gl.deleteBuffer(this._bufferData.vertex); + this._bufferData.vertex = null; + this.gl.deleteBuffer(this._bufferData.uv); + this._bufferData.uv = null; + this.gl.deleteBuffer(this._bufferData.index); + this._bufferData.index = null; + this._bufferData = null; + + this._textures = null; + + for (let i = 0; i < this._modelRenderTargets.length; i++) { + if ( + this._modelRenderTargets[i] != null && + this._modelRenderTargets[i].isValid() + ) { + this._modelRenderTargets[i].destroyRenderTarget(); + } + } + this._modelRenderTargets.length = 0; + this._modelRenderTargets = null; + + for (let i = 0; i < this._drawableMasks.length; i++) { + if (this._drawableMasks[i] != null && this._drawableMasks[i].isValid()) { + this._drawableMasks[i].destroyRenderTarget(); + } + } + this._drawableMasks.length = 0; + this._drawableMasks = null; + + for (let i = 0; i < this._offscreenMasks.length; i++) { + if ( + this._offscreenMasks[i] != null && + this._offscreenMasks[i].isValid() + ) { + this._offscreenMasks[i].destroyRenderTarget(); + } + } + this._offscreenMasks.length = 0; + this._offscreenMasks = null; + + for (let i = 0; i < this._offscreenList.length; i++) { + if (this._offscreenList[i] != null && this._offscreenList[i].isValid()) { + this._offscreenList[i].destroyRenderTarget(); + } + } + this._offscreenList.length = 0; + this._offscreenList = null; + + this._offscreenClippingManager = null; + this._drawableClippingManager = null; + this._clippingContextBufferForMask = null; + this._clippingContextBufferForDraw = null; + + this._rendererProfile = null; + this._sortedObjectsIndexList = null; + this._sortedObjectsTypeList = null; + this._currentFbo = null; + this._model = null; + this.gl = null; + } + + /** + * Shaderの読み込みを行う + * @param shaderPath シェーダのパス + */ + public loadShaders(shaderPath: string = null): void { + if (this.gl == null) { + CubismLogError( + "'gl' is null. WebGLRenderingContext is required.\nPlease call 'CubimRenderer_WebGL.startUp' function." + ); + return; + } + + if ( + CubismShaderManager_WebGL.getInstance().getShader(this.gl)._shaderSets + .length == 0 || + !CubismShaderManager_WebGL.getInstance().getShader(this.gl) + ._isShaderLoaded + ) { + const shader = CubismShaderManager_WebGL.getInstance().getShader(this.gl); + if (shaderPath != null) { + shader.setShaderPath(shaderPath); + } + shader.generateShaders(); + } + } + + /** + * モデルを描画する実際の処理 + * @param shaderPath シェーダのパス + */ + public doDrawModel(shaderPath: string = null): void { + this.loadShaders(shaderPath); + this.beforeDrawModelRenderTarget(); + + const lastFbo = this.gl.getParameter( + this.gl.FRAMEBUFFER_BINDING + ) as WebGLFramebuffer; + const lastViewport = this.gl.getParameter(this.gl.VIEWPORT) as number[]; + + // //------------ クリッピングマスク・バッファ前処理方式の場合 ------------ + if (this._drawableClippingManager != null) { + this.preDraw(); + for ( + let i = 0; + i < this._drawableClippingManager.getRenderTextureCount(); + ++i + ) { + if ( + this._drawableMasks[i].getBufferWidth() != + this._drawableClippingManager.getClippingMaskBufferSize() || + this._drawableMasks[i].getBufferHeight() != + this._drawableClippingManager.getClippingMaskBufferSize() + ) { + // クリッピングマスクのサイズが変更された場合は、オフスクリーンサーフェスを再作成する + this._drawableMasks[i].createRenderTarget( + this.gl, + this._drawableClippingManager.getClippingMaskBufferSize(), + this._drawableClippingManager.getClippingMaskBufferSize(), + lastFbo + ); + } + } + + if (this.isUsingHighPrecisionMask()) { + this._drawableClippingManager.setupMatrixForHighPrecision( + this.getModel(), + false + ); + } else { + this._drawableClippingManager.setupClippingContext( + this.getModel(), + this, + lastFbo, + lastViewport, + DrawableObjectType.DrawableObjectType_Drawable + ); + } + } + + if (this._offscreenClippingManager != null) { + this.preDraw(); + + // サイズが違う場合はここで作成しなおし + for ( + let i = 0; + i < this._offscreenClippingManager.getRenderTextureCount(); + ++i + ) { + if ( + this._offscreenMasks[i].getBufferWidth() != + this._offscreenClippingManager.getClippingMaskBufferSize() || + this._offscreenMasks[i].getBufferHeight() != + this._offscreenClippingManager.getClippingMaskBufferSize() + ) { + this._offscreenMasks[i].createRenderTarget( + this.gl, + this._offscreenClippingManager.getClippingMaskBufferSize(), + this._offscreenClippingManager.getClippingMaskBufferSize(), + lastFbo + ); + } + } + + if (this.isUsingHighPrecisionMask()) { + this._offscreenClippingManager.setupMatrixForOffscreenHighPrecision( + this.getModel(), + false, + this.getMvpMatrix() + ); + } else { + this._offscreenClippingManager.setupClippingContext( + this.getModel(), + this, + lastFbo, + lastViewport, + DrawableObjectType.DrawableObjectType_Offscreen + ); + } + } + + // 上記クリッピング処理内でも一度PreDrawを呼ぶので注意!! + this.preDraw(); + + this.drawObjectLoop(lastFbo); + + this.afterDrawModelRenderTarget(); + } + + /** + * 描画オブジェクトのループ処理を行う。 + * + * @param lastFbo 前回のフレームバッファ + */ + public drawObjectLoop(lastFbo: WebGLFramebuffer): void { + const model = this.getModel(); + const drawableCount = model.getDrawableCount(); + const offscreenCount = model.getOffscreenCount(); + const totalCount = drawableCount + offscreenCount; + const renderOrder = model.getRenderOrders(); + + this._currentOffscreen = null; // 現在のオフスクリーンを初期化 + this._currentFbo = lastFbo; + this._modelRootFbo = lastFbo; + + // インデックスを描画順でソート + for (let i = 0; i < totalCount; ++i) { + const order = renderOrder[i]; + + if (i < drawableCount) { + this._sortedObjectsIndexList[order] = i; + this._sortedObjectsTypeList[order] = + DrawableObjectType.DrawableObjectType_Drawable; + } else if (i < totalCount) { + this._sortedObjectsIndexList[order] = i - drawableCount; + this._sortedObjectsTypeList[order] = + DrawableObjectType.DrawableObjectType_Offscreen; + } + } + + // 描画 + for (let i = 0; i < totalCount; ++i) { + const objectIndex = this._sortedObjectsIndexList[i]; + const objectType = this._sortedObjectsTypeList[i]; + + this.renderObject(objectIndex, objectType); + } + + while (this._currentOffscreen != null) { + // オフスクリーンが残っている場合は親オフスクリーンへの伝搬を行う + this.submitDrawToParentOffscreen( + this._currentOffscreen.getOffscreenIndex(), + DrawableObjectType.DrawableObjectType_Offscreen + ); + } + } + + /** + * 描画オブジェクトを描画する。 + * + * @param objectIndex 描画対象のオブジェクトのインデックス + * @param objectType 描画対象のオブジェクトのタイプ + * @param lastFbo 前回のフレームバッファ + * @param lastViewport 前回のビューポート + */ + protected renderObject( + objectIndex: number, + objectType: DrawableObjectType + ): void { + switch (objectType) { + case DrawableObjectType.DrawableObjectType_Drawable: + this.drawDrawable(objectIndex, this._modelRootFbo); + break; + case DrawableObjectType.DrawableObjectType_Offscreen: + this.addOffscreen(objectIndex); + break; + default: + CubismLogError('Unknown object type: ' + objectType); + break; + } + } + + /** + * 描画オブジェクト(アートメッシュ)を描画する。 + * + * @param model 描画対象のモデル + * @param index 描画対象のメッシュのインデックス + */ + public drawDrawable(drawableIndex: number, rootFbo: WebGLFramebuffer): void { + // Drawableが表示状態でなければ処理をパスする + if (!this.getModel().getDrawableDynamicFlagIsVisible(drawableIndex)) { + return; + } + + this.submitDrawToParentOffscreen( + drawableIndex, + DrawableObjectType.DrawableObjectType_Drawable + ); + + const clipContext = + this._drawableClippingManager != null + ? this._drawableClippingManager.getClippingContextListForDraw()[ + drawableIndex + ] + : null; + + if (clipContext != null && this.isUsingHighPrecisionMask()) { + // 描くことになっていた + if (clipContext._isUsing) { + // 生成したFrameBufferと同じサイズでビューポートを設定 + this.gl.viewport( + 0, + 0, + this._drawableClippingManager.getClippingMaskBufferSize(), + this._drawableClippingManager.getClippingMaskBufferSize() + ); + + this.preDraw(); // バッファをクリアする + + // ---------- マスク描画処理 ---------- + // マスク用RenderTextureをactiveにセット + this.getDrawableMaskBuffer(clipContext._bufferIndex).beginDraw( + this._currentFbo + ); + + // マスクをクリアする + // (仮仕様) 1が無効(描かれない)領域、0が有効(描かれる)領域。(シェーダーCd*Csで0に近い値をかけてマスクを作る。1をかけると何も起こらない) + this.gl.clearColor(1.0, 1.0, 1.0, 1.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + } + + { + const clipDrawCount: number = clipContext._clippingIdCount; + + for (let index = 0; index < clipDrawCount; index++) { + const clipDrawIndex: number = clipContext._clippingIdList[index]; + + // 頂点情報が更新されておらず、信頼性がない場合は描画をパスする + if ( + !this._model.getDrawableDynamicFlagVertexPositionsDidChange( + clipDrawIndex + ) + ) { + continue; + } + + this.setIsCulling( + this._model.getDrawableCulling(clipDrawIndex) != false + ); + + // 今回専用の変換を適用して描く + // チャンネルも切り替える必要がある(A,R,G,B) + this.setClippingContextBufferForMask(clipContext); + + this.drawMeshWebGL(this._model, clipDrawIndex); + } + + // --- 後処理 --- + this.getDrawableMaskBuffer(clipContext._bufferIndex).endDraw(); + this.setClippingContextBufferForMask(null); + + this.gl.viewport( + 0, + 0, + this._modelRenderTargetWidth, + this._modelRenderTargetHeight + ); + + this.preDraw(); // バッファをクリアする + } + } + + // クリッピングマスクをセットする + this.setClippingContextBufferForDrawable(clipContext); + + this.setIsCulling(this.getModel().getDrawableCulling(drawableIndex)); + + this.drawMeshWebGL(this._model, drawableIndex); + } + + /** + * 描画オブジェクト(アートメッシュ)を描画する。 + * + * @param model 描画対象のモデル + * @param index 描画対象のメッシュのインデックス + */ + public drawMeshWebGL(model: Readonly, index: number): void { + // 裏面描画の有効・無効 + if (this.isCulling()) { + this.gl.enable(this.gl.CULL_FACE); + } else { + this.gl.disable(this.gl.CULL_FACE); + } + + this.gl.frontFace(this.gl.CCW); // Cubism SDK OpenGLはマスク・アートメッシュ共にCCWが表面 + + if (this.isGeneratingMask()) { + CubismShaderManager_WebGL.getInstance() + .getShader(this.gl) + .setupShaderProgramForMask(this, model, index); + } else { + CubismShaderManager_WebGL.getInstance() + .getShader(this.gl) + .setupShaderProgramForDrawable(this, model, index); + } + + if ( + !CubismShaderManager_WebGL.getInstance().getShader(this.gl) + ._isShaderLoaded + ) { + // シェーダーがロードされていない場合は描画を行わない + // NOTE: Cubism 5.2 以前のモデル描画時にのみ、マスク無しのモデルが描画されてしまうためここで早期リターン + return; + } + + { + const indexCount: number = model.getDrawableVertexIndexCount(index); + this.gl.drawElements( + this.gl.TRIANGLES, + indexCount, + this.gl.UNSIGNED_SHORT, + 0 + ); + } + + // 後処理 + this.gl.useProgram(null); + this.setClippingContextBufferForDrawable(null); + this.setClippingContextBufferForMask(null); + } + + /** + * オフスクリーンを親のオフスクリーンにコピーする。 + * + * @param objectIndex オブジェクトのインデックス + * @param objectType オブジェクトの種類 + */ + submitDrawToParentOffscreen( + objectIndex: number, + objectType: DrawableObjectType + ): void { + if (this._currentOffscreen == null || objectIndex == s_invalidValue) { + return; + } + + const currentOwnerIndex = + this.getModel().getOffscreenOwnerIndices()[ + this._currentOffscreen.getOffscreenIndex() + ]; + + // オーナーが不明な場合は処理を終了 + if (currentOwnerIndex == s_invalidValue) { + return; + } + + let targetParentIndex = NoParentIndex; + + switch (objectType) { + case DrawableObjectType.DrawableObjectType_Drawable: + targetParentIndex = + this.getModel().getDrawableParentPartIndex(objectIndex); + break; + case DrawableObjectType.DrawableObjectType_Offscreen: + targetParentIndex = + this.getModel().getPartParentPartIndices()[ + this.getModel().getOffscreenOwnerIndices()[objectIndex] + ]; + break; + default: + // 不明なタイプだった場合は処理を終了 + return; + } + while (targetParentIndex != NoParentIndex) { + // オブジェクトの親が現在のオーナーと同じ場合は処理を終了 + if (targetParentIndex == currentOwnerIndex) { + return; + } + + targetParentIndex = + this.getModel().getPartParentPartIndices()[targetParentIndex]; + } + + // 描画 + this.drawOffscreen(this._currentOffscreen); + + // さらに親のオフスクリーンに伝搬可能なら伝搬する + this.submitDrawToParentOffscreen(objectIndex, objectType); + } + + /** + * 描画オブジェクト(オフスクリーン)を追加する。 + * + * @param offscreenIndex オフスクリーンのインデックス + */ + public addOffscreen(offscreenIndex: number): void { + // 以前のオフスクリーンレンダリングターゲットを親に伝搬する処理を追加する + if ( + this._currentOffscreen != null && + this._currentOffscreen.getOffscreenIndex() != offscreenIndex + ) { + let isParent = false; + const ownerIndex = + this.getModel().getOffscreenOwnerIndices()[offscreenIndex]; + let parentIndex = this.getModel().getPartParentPartIndices()[ownerIndex]; + + const currentOffscreenIndex = this._currentOffscreen.getOffscreenIndex(); + const currentOffscreenOwnerIndex = + this.getModel().getOffscreenOwnerIndices()[currentOffscreenIndex]; + while (parentIndex != NoParentIndex) { + if (parentIndex == currentOffscreenOwnerIndex) { + isParent = true; + break; + } + parentIndex = this.getModel().getPartParentPartIndices()[parentIndex]; + } + + if (!isParent) { + // 現在のオフスクリーンレンダリングターゲットがあるなら、親に伝搬する + this.submitDrawToParentOffscreen( + offscreenIndex, + DrawableObjectType.DrawableObjectType_Offscreen + ); + } + } + + const offscreen = this._offscreenList[offscreenIndex]; + + // レンダーターゲットが未生成、レンダーテクスチャ使用中、もしくはサイズが異なるなら新しいオフスクリーンレンダリングターゲットを作成 + if ( + offscreen.getRenderTexture() == null || + offscreen.getBufferWidth() != this._modelRenderTargetWidth || + offscreen.getBufferHeight() != this._modelRenderTargetHeight || + offscreen.getUsingRenderTextureState() + ) { + offscreen.setOffscreenRenderTarget( + this.gl, + this._modelRenderTargetWidth, + this._modelRenderTargetHeight, + this._currentFbo + ); + } else { + // 既存のRenderTextureを使用するので使用フラグを立てる。 + offscreen.startUsingRenderTexture(); + } + + // 以前のオフスクリーンレンダリングターゲットを取得 + const oldOffscreen = offscreen.getParentPartOffscreen(); + offscreen.setOldOffscreen(oldOffscreen); + + let oldFBO: WebGLFramebuffer = null; + if (oldOffscreen != null) { + oldFBO = oldOffscreen.getRenderTexture(); + } + if (oldFBO == null) { + oldFBO = this._modelRootFbo; // ルートのFBOを使用 + } + + // 別バッファに描画を開始 + offscreen.beginDraw(oldFBO); + this.gl.viewport( + 0, + 0, + this._modelRenderTargetWidth, + this._modelRenderTargetHeight + ); + offscreen.clear(0.0, 0.0, 0.0, 0.0); + + // 現在のオフスクリーンレンダリングターゲットを設定 + this._currentOffscreen = offscreen; + this._currentFbo = offscreen.getRenderTexture(); + } + + /** + * オフスクリーン描画を行う。 + * + * @param offscreen オフスクリーンレンダリングターゲット + */ + public drawOffscreen(offscreen: CubismOffscreenRenderTarget_WebGL): void { + const offscreenIndex = offscreen.getOffscreenIndex(); + + // クリッピングマスク + const clipContext = + this._offscreenClippingManager != null + ? this._offscreenClippingManager.getClippingContextListForOffscreen()[ + offscreenIndex + ] + : null; + + if (clipContext != null && this.isUsingHighPrecisionMask()) { + // マスクを書く必要がある + if (clipContext._isUsing) { + // 書くことになっていた + // 生成したRenderTargetと同じサイズでビューポートを設定 + this.gl.viewport( + 0, + 0, + this._offscreenClippingManager.getClippingMaskBufferSize(), + this._offscreenClippingManager.getClippingMaskBufferSize() + ); + + this.preDraw(); // バッファをクリアする + + // ---------- マスク描画処理 ---------- + // マスク用RenderTextureをactiveにセット + this.getOffscreenMaskBuffer(clipContext._bufferIndex).beginDraw( + this._currentFbo + ); + + // マスクをクリアする + // 1が無効(描かれない)領域、0が有効(描かれる)領域。(シェーダで Cd*Csで0に近い値をかけてマスクを作る。1をかけると何も起こらない) + this.gl.clearColor(1.0, 1.0, 1.0, 1.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + } + + { + const clipDrawCount = clipContext._clippingIdCount; + for (let index = 0; index < clipDrawCount; index++) { + const clipDrawIndex = clipContext._clippingIdList[index]; + + // 頂点情報が更新されておらず、信頼性がない場合は描画をパスする + if ( + !this.getModel().getDrawableDynamicFlagVertexPositionsDidChange( + clipDrawIndex + ) + ) { + continue; + } + + this.setIsCulling( + this.getModel().getDrawableCulling(clipDrawIndex) != false + ); + + // 今回専用の変換を適用して描く + // チャンネルも切り替える必要がある(A,R,G,B) + this.setClippingContextBufferForMask(clipContext); + + this.drawMeshWebGL(this.getModel(), clipDrawIndex); + } + } + + { + // --- 後処理 --- + this.getOffscreenMaskBuffer(clipContext._bufferIndex).endDraw(); + this.setClippingContextBufferForMask(null); + this.gl.viewport( + 0, + 0, + this._modelRenderTargetWidth, + this._modelRenderTargetHeight + ); + + this.preDraw(); // バッファをクリアする + } + } + + // クリッピングマスクをセットする + this.setClippingContextBufferForOffscreen(clipContext); + + this.setIsCulling(this._model.getOffscreenCulling(offscreenIndex) != false); + + this.drawOffscreenWebGL(this.getModel(), offscreen); + } + + /** + * オフスクリーン描画のWebGL実装 + * + * @param model モデル + * @param index オフスクリーンインデックス + */ + public drawOffscreenWebGL( + model: Readonly, + offscreen: CubismOffscreenRenderTarget_WebGL + ): void { + // 裏面描画の有効・無効 + if (this.isCulling()) { + this.gl.enable(this.gl.CULL_FACE); + } else { + this.gl.disable(this.gl.CULL_FACE); + } + + this.gl.frontFace(this.gl.CCW); // Cubism SDK OpenGLはマスク・アートメッシュ共にCCWが表面 + + CubismShaderManager_WebGL.getInstance() + .getShader(this.gl) + .setupShaderProgramForOffscreen(this, model, offscreen); + + offscreen.endDraw(); + this._currentOffscreen = this._currentOffscreen.getOldOffscreen(); + this._currentFbo = offscreen.getOldFBO(); + if (this._currentFbo == null) { + this._currentOffscreen = this._modelRenderTargets[0]; + this._currentFbo = this._modelRenderTargets[0].getRenderTexture(); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this._currentFbo); + } + + // ポリゴンメッシュを描画する + { + // インデックスバッファの作成とバインド + const indexBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, indexBuffer); + this.gl.bufferData( + this.gl.ELEMENT_ARRAY_BUFFER, + s_renderTargetIndexArray, + this.gl.STATIC_DRAW + ); + + // 描画 + this.gl.drawElements( + this.gl.TRIANGLES, + s_renderTargetIndexArray.length, + this.gl.UNSIGNED_SHORT, + 0 + ); + this.gl.deleteBuffer(indexBuffer); + } + + // 後処理 + offscreen.stopUsingRenderTexture(); + this.gl.useProgram(null); + this.setClippingContextBufferForMask(null); + this.setClippingContextBufferForOffscreen(null); + } + + /** + * モデル描画直前のレンダラのステートを保持する + */ + protected saveProfile(): void { + this._rendererProfile.save(); + } + + /** + * モデル描画直前のレンダラのステートを復帰させる + */ + protected restoreProfile(): void { + this._rendererProfile.restore(); + } + + /** + * モデル描画直前のオフスクリーン設定を行う + */ + public beforeDrawModelRenderTarget(): void { + if (this._modelRenderTargets.length == 0) { + return; + } + + // オフスクリーンのバッファのサイズが違う場合は作り直し + for (let i = 0; i < this._modelRenderTargets.length; ++i) { + if ( + this._modelRenderTargets[i].getBufferWidth() != + this._modelRenderTargetWidth || + this._modelRenderTargets[i].getBufferHeight() != + this._modelRenderTargetHeight + ) { + this._modelRenderTargets[i].createRenderTarget( + this.gl, + this._modelRenderTargetWidth, + this._modelRenderTargetHeight, + this._currentFbo + ); + } + } + + // 別バッファに描画を開始 + this._modelRenderTargets[0].beginDraw(); + this._modelRenderTargets[0].clear(0.0, 0.0, 0.0, 0.0); + } + + /** + * モデル描画後のオフスクリーン設定を行う + */ + public afterDrawModelRenderTarget(): void { + if (this._modelRenderTargets.length == 0) { + return; + } + + // 元のバッファに描画する + this._modelRenderTargets[0].endDraw(); + + CubismShaderManager_WebGL.getInstance() + .getShader(this.gl) + .setupShaderProgramForOffscreenRenderTarget(this); + + if ( + CubismShaderManager_WebGL.getInstance().getShader(this.gl)._isShaderLoaded + ) { + // インデックスバッファの作成とバインド + const indexBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, indexBuffer); + this.gl.bufferData( + this.gl.ELEMENT_ARRAY_BUFFER, + s_renderTargetIndexArray, + this.gl.STATIC_DRAW + ); + + // 描画 + this.gl.drawElements( + this.gl.TRIANGLES, + s_renderTargetIndexArray.length, + this.gl.UNSIGNED_SHORT, + 0 + ); + this.gl.deleteBuffer(indexBuffer); + } + + this.gl.useProgram(null); + } + + /** + * オフスクリーンのクリッピングマスクのバッファを取得する + * + * @param index オフスクリーンのクリッピングマスクのバッファのインデックス + * + * @return オフスクリーンのクリッピングマスクのバッファへのポインタ + */ + getOffscreenMaskBuffer(index: number): CubismRenderTarget_WebGL { + return this._offscreenMasks[index]; + } + + /** + * レンダラが保持する静的なリソースを解放する + * WebGLの静的なシェーダープログラムを解放する + */ + public static doStaticRelease(): void { + CubismShaderManager_WebGL.deleteInstance(); + } + + /** + * レンダーステートを設定する + * + * @param fbo アプリケーション側で指定しているフレームバッファ + * @param viewport ビューポート + */ + public setRenderState(fbo: WebGLFramebuffer, viewport: number[]): void { + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, fbo); + this.gl.viewport(viewport[0], viewport[1], viewport[2], viewport[3]); + + if ( + this._modelRenderTargetWidth != viewport[2] || + this._modelRenderTargetHeight != viewport[3] + ) { + this._modelRenderTargetWidth = viewport[2]; + this._modelRenderTargetHeight = viewport[3]; + } + } + + /** + * 描画開始時の追加処理 + * モデルを描画する前にクリッピングマスクに必要な処理を実装している + */ + public preDraw(): void { + this.gl.disable(this.gl.SCISSOR_TEST); + this.gl.disable(this.gl.STENCIL_TEST); + this.gl.disable(this.gl.DEPTH_TEST); + + // カリング(1.0beta3) + this.gl.frontFace(this.gl.CW); + + this.gl.enable(this.gl.BLEND); + this.gl.colorMask(true, true, true, true); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); // 前にバッファがバインドされていたら破棄する必要がある + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null); + + // 異方性フィルタリングを適用する + if (this.getAnisotropy() > 0.0 && this._extension) { + for (let i = 0; i < this._textures.size; ++i) { + this.gl.bindTexture(this.gl.TEXTURE_2D, this._textures.get(i)); + this.gl.texParameterf( + this.gl.TEXTURE_2D, + this._extension.TEXTURE_MAX_ANISOTROPY_EXT, + this.getAnisotropy() + ); + } + } + } + + /** + * Drawableのマスク用のオフスクリーンサーフェースを取得する + * + * @param index オフスクリーンサーフェースのインデックス + * + * @return マスク用のオフスクリーンサーフェース + */ + public getDrawableMaskBuffer(index: number): CubismRenderTarget_WebGL { + return this._drawableMasks[index]; + } + + /** + * マスクテクスチャに描画するクリッピングコンテキストをセットする + */ + public setClippingContextBufferForMask(clip: CubismClippingContext_WebGL) { + this._clippingContextBufferForMask = clip; + } + + /** + * マスクテクスチャに描画するクリッピングコンテキストを取得する + * + * @return マスクテクスチャに描画するクリッピングコンテキスト + */ + public getClippingContextBufferForMask(): CubismClippingContext_WebGL { + return this._clippingContextBufferForMask; + } + + /** + * Drawableの画面上に描画するクリッピングコンテキストをセットする + * + * @param clip drawableで画面上に描画するクリッピングコンテキスト + */ + public setClippingContextBufferForDrawable( + clip: CubismClippingContext_WebGL + ): void { + this._clippingContextBufferForDraw = clip; + } + + /** + * Drawableの画面上に描画するクリッピングコンテキストを取得する + * + * @return Drawableの画面上に描画するクリッピングコンテキスト + */ + public getClippingContextBufferForDrawable(): CubismClippingContext_WebGL { + return this._clippingContextBufferForDraw; + } + + /** + * offscreenで画面上に描画するクリッピングコンテキストをセットする。 + * + * @param clip offscreenで画面上に描画するクリッピングコンテキスト + */ + public setClippingContextBufferForOffscreen( + clip: CubismClippingContext_WebGL + ): void { + this._clippingContextBufferForOffscreen = clip; + } + + /** + * offscreenで画面上に描画するクリッピングコンテキストを取得する。 + * + * @return offscreenで画面上に描画するクリッピングコンテキスト + */ + public getClippingContextBufferForOffscreen(): CubismClippingContext_WebGL { + return this._clippingContextBufferForOffscreen; + } + + /** + * マスク生成時かを判定する + * + * @return 判定値 + */ + public isGeneratingMask() { + return this.getClippingContextBufferForMask() != null; + } + + /** + * glの設定 + */ + public startUp(gl: WebGLRenderingContext | WebGL2RenderingContext): void { + this.gl = gl; + + if (this._drawableClippingManager) { + this._drawableClippingManager.setGL(gl); + } + + if (this._offscreenClippingManager) { + this._offscreenClippingManager.setGL(gl); + } + + CubismShaderManager_WebGL.getInstance().setGlContext(gl); + this._rendererProfile.setGl(gl); + + // 異方性フィルタリングが使用できるかチェック + this._extension = + this.gl.getExtension('EXT_texture_filter_anisotropic') || + this.gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') || + this.gl.getExtension('MOZ_EXT_texture_filter_anisotropic'); + + if (this._model.isUsingMasking()) { + this._drawableMasks.length = + this._drawableClippingManager.getRenderTextureCount(); + for (let i = 0; i < this._drawableMasks.length; ++i) { + const renderTarget = new CubismRenderTarget_WebGL(); + renderTarget.createRenderTarget( + this.gl, + this._drawableClippingManager.getClippingMaskBufferSize(), + this._drawableClippingManager.getClippingMaskBufferSize(), + this._currentFbo + ); + this._drawableMasks[i] = renderTarget; + } + } + + if (this._model.isBlendModeEnabled()) { + // オフスクリーンの作成 + this._modelRenderTargets.length = 0; + + // TextureBarrierの代替用にオフスクリーンを2つ作成する + const createSize = 3; + this._modelRenderTargets.length = createSize; + for (let i = 0; i < createSize; ++i) { + const offscreenRenderTarget: CubismOffscreenRenderTarget_WebGL = + new CubismOffscreenRenderTarget_WebGL(); + offscreenRenderTarget.createRenderTarget( + this.gl, + this._modelRenderTargetWidth, + this._modelRenderTargetHeight, + this._currentFbo + ); + this._modelRenderTargets[i] = offscreenRenderTarget; + } + + if (this._model.isUsingMaskingForOffscreen()) { + this._offscreenMasks.length = + this._offscreenClippingManager.getRenderTextureCount(); + for (let i = 0; i < this._offscreenMasks.length; ++i) { + const offscreenMask = new CubismRenderTarget_WebGL(); + offscreenMask.createRenderTarget( + this.gl, + this._offscreenClippingManager.getClippingMaskBufferSize(), + this._offscreenClippingManager.getClippingMaskBufferSize(), + this._currentFbo + ); + this._offscreenMasks[i] = offscreenMask; + } + } + + const offscreenCount = this._model.getOffscreenCount(); + // オフスクリーンの数が0の場合は何もしない + if (offscreenCount > 0) { + this._offscreenList = new Array( + offscreenCount + ); + for ( + let offscreenIndex = 0; + offscreenIndex < offscreenCount; + ++offscreenIndex + ) { + const offscreenRenderTarget = new CubismOffscreenRenderTarget_WebGL(); + offscreenRenderTarget.setOffscreenIndex(offscreenIndex); + this._offscreenList[offscreenIndex] = offscreenRenderTarget; + } + + // 全てのオフスクリーンを登録し終わってから行う + this.setupParentOffscreens(this._model, offscreenCount); + } + } + + // 描画対象を初期状態に戻す + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this._currentFbo); + } + + _textures: Map; // モデルが参照するテクスチャとレンダラでバインドしているテクスチャとのマップ + _sortedObjectsIndexList: Array; // 描画オブジェクトのインデックスを描画順に並べたリスト + _sortedObjectsTypeList: Array; // 描画オブジェクトのオブジェクトタイプを描画順に並べたリスト + _rendererProfile: CubismRendererProfile_WebGL; + _drawableClippingManager: CubismClippingManager_WebGL; // クリッピングマスク管理オブジェクト + _clippingContextBufferForMask: CubismClippingContext_WebGL; // マスクテクスチャに描画するためのクリッピングコンテキスト + _clippingContextBufferForDraw: CubismClippingContext_WebGL; // 画面上描画するためのクリッピングコンテキスト + _clippingContextBufferForOffscreen: CubismClippingContext_WebGL; // オフスクリーン描画用のクリッピングコンテキスト + _offscreenClippingManager: CubismClippingManager_WebGL; // オフスクリーン描画用のクリッピングマスク管理オブジェクト + + _modelRenderTargets: Array; ///< モデル全体を描画する先のフレームバッファ + + _drawableMasks: Array; // マスク用のオフスクリーンサーフェースのリスト + _offscreenMasks: Array; ///< オフスクリーン機能マスク描画用のフレームバッファ + + _offscreenList: Array; ///< モデルのオフスクリーン + _currentFbo: WebGLFramebuffer; ///< 現在のフレームバッファオブジェクト + _currentOffscreen: CubismOffscreenRenderTarget_WebGL | null; // 現在のオフスクリーン + + _modelRootFbo: WebGLFramebuffer; // モデルのルートフレームバッファ + + _bufferData: { + vertex: WebGLBuffer; + uv: WebGLBuffer; + index: WebGLBuffer; + }; // 頂点バッファデータ + _extension: any; // 拡張機能 + gl: WebGLRenderingContext | WebGL2RenderingContext; // webglコンテキスト +} + +/** + * レンダラが保持する静的なリソースを開放する + */ +CubismRenderer.staticRelease = (): void => { + CubismRenderer_WebGL.doStaticRelease(); +}; + +// Namespace definition for compatibility. +import * as $ from './cubismrenderer_webgl'; +import { CubismRenderTarget_WebGL as CubismRenderTarget_WebGL } from './cubismrendertarget_webgl'; +import { CubismOffscreenRenderTarget_WebGL as CubismOffscreenRenderTarget_WebGL } from './cubismoffscreenrendertarget_webgl'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismClippingContext = $.CubismClippingContext_WebGL; + export type CubismClippingContext = $.CubismClippingContext_WebGL; + export const CubismClippingManager_WebGL = $.CubismClippingManager_WebGL; + export type CubismClippingManager_WebGL = $.CubismClippingManager_WebGL; + export const CubismRenderer_WebGL = $.CubismRenderer_WebGL; + export type CubismRenderer_WebGL = $.CubismRenderer_WebGL; +} diff --git a/avatar-h5-renderer/framework/src/rendering/cubismrendertarget_webgl.ts b/avatar-h5-renderer/framework/src/rendering/cubismrendertarget_webgl.ts new file mode 100644 index 0000000..9fd11a7 --- /dev/null +++ b/avatar-h5-renderer/framework/src/rendering/cubismrendertarget_webgl.ts @@ -0,0 +1,289 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismLogError } from '../utils/cubismdebug'; + +/** + * WebGL用オフスクリーンサーフェス + * マスクの描画に必要なフレームバッファなどを管理する。 + */ +export class CubismRenderTarget_WebGL { + /** + * WebGL2RenderingContext.blitFramebuffer() でバッファのコピーを行う。 + * + * @param src コピー元のオフスクリーンサーフェス + * @param dst コピー先のオフスクリーンサーフェス + */ + public static copyBuffer( + gl: WebGL2RenderingContext, + src: CubismRenderTarget_WebGL, + dst: CubismRenderTarget_WebGL + ): void { + if (src == null || dst == null) { + return; + } + + if (!(gl instanceof WebGL2RenderingContext)) { + throw new Error('WebGL2RenderingContext is required for buffer copy.'); + } + + const previousFramebuffer = gl.getParameter( + gl.FRAMEBUFFER_BINDING + ) as WebGLFramebuffer; + + // 各オフスクリーンサーフェスのレンダーテクスチャをバインド + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, src.getRenderTexture()); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dst.getRenderTexture()); + + // バッファのコピーを実行 + gl.blitFramebuffer( + 0, + 0, + src.getBufferWidth(), + src.getBufferHeight(), + 0, + 0, + dst.getBufferWidth(), + dst.getBufferHeight(), + gl.COLOR_BUFFER_BIT, + gl.NEAREST + ); + + // コピー後、元のフレームバッファを復元 + gl.bindFramebuffer(gl.FRAMEBUFFER, previousFramebuffer); + } + + /** + * 描画を開始する。 + * + * @param restoreFbo EndDraw時に復元するFBOを指定する。nullを指定すると、beginDraw時に現在のFBOを記憶しておく。 + */ + public beginDraw(restoreFbo: WebGLFramebuffer = null): void { + if (this._renderTexture == null) { + console.error('_renderTexture is null'); + return; + } + + // バックバッファのサーフェイスを記憶しておく。 + if (restoreFbo == null) { + this._oldFbo = this._gl.getParameter(this._gl.FRAMEBUFFER_BINDING); + } else { + this._oldFbo = restoreFbo; + } + + // RenderTextureをactiveにセット + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, this._renderTexture); + } + + /** + * 描画を終了し、バックバッファのサーフェイスを復元する。 + */ + public endDraw(): void { + // バックバッファのサーフェイスを復元 + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, this._oldFbo); + } + + /** + * バインドされているカラーバッファのクリアを行う。 + * + * @param r 赤の成分 (0.0 - 1.0) + * @param g 緑の成分 (0.0 - 1.0) + * @param b 青の成分 (0.0 - 1.0) + * @param a アルファの成分 (0.0 - 1.0) + */ + public clear(r: number, g: number, b: number, a: number): void { + // クリア処理 + this._gl.clearColor(r, g, b, a); + this._gl.clear(this._gl.COLOR_BUFFER_BIT); + } + + /** + * オフスクリーンサーフェスを作成する。 + * + * @param gl WebGLRenderingContextまたはWebGL2RenderingContext + * NOTE: Cubism 5.3以降のモデルが使用される場合はWebGL2RenderingContextを使用すること。 + * @param displayBufferWidth オフスクリーンサーフェスの幅 + * @param displayBufferHeight オフスクリーンサーフェスの高さ + * @param previousFramebuffer 前のフレームバッファ + * + * @return 成功した場合はtrue、失敗した場合はfalse + */ + public createRenderTarget( + gl: WebGLRenderingContext | WebGL2RenderingContext, + displayBufferWidth: number, + displayBufferHeight: number, + previousFramebuffer: WebGLFramebuffer + ): boolean { + this.destroyRenderTarget(); + + this._colorBuffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._colorBuffer); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + displayBufferWidth, + displayBufferHeight, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + + gl.bindTexture(gl.TEXTURE_2D, null); + + // フレームバッファを作成 + const ret = gl.createFramebuffer(); + if (ret == null) { + CubismLogError('Failed to create framebuffer'); + return false; + } + + // 作成したフレームバッファをバインド + gl.bindFramebuffer(gl.FRAMEBUFFER, ret); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this._colorBuffer, + 0 + ); + + // 状態をチェック + const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + + // フレームバッファが完全でない場合はエラーを出力して以前のフレームバッファを復元 + if (status !== gl.FRAMEBUFFER_COMPLETE) { + CubismLogError('Framebuffer is not complete'); + gl.bindFramebuffer(gl.FRAMEBUFFER, previousFramebuffer); + gl.deleteFramebuffer(ret); + + this.destroyRenderTarget(); + + return false; + } + + this._renderTexture = ret; + this._bufferWidth = displayBufferWidth; + this._bufferHeight = displayBufferHeight; + + this._gl = gl; + + return true; + } + + /** + * レンダーターゲットを破棄する。 + */ + public destroyRenderTarget(): void { + if (this._colorBuffer) { + this._gl.bindTexture(this._gl.TEXTURE_2D, null); + this._gl.deleteTexture(this._colorBuffer); + this._colorBuffer = null; + } + + if (this._renderTexture) { + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null); + this._gl.deleteFramebuffer(this._renderTexture); + this._renderTexture = null; + } + } + + /** + * WebGLのコンテキストを取得する。 + * + * @return WebGLRenderingContextまたはWebGL2RenderingContext + */ + public getGL(): WebGLRenderingContext | WebGL2RenderingContext { + return this._gl; + } + + /** + * レンダーテクスチャを取得する。 + * + * @return WebGLFramebuffer + */ + public getRenderTexture(): WebGLFramebuffer { + return this._renderTexture; + } + + /** + * カラーバッファを取得する。 + * + * @return WebGLTexture + */ + public getColorBuffer(): WebGLTexture { + return this._colorBuffer; + } + + /** + * カラーバッファの幅を取得する。 + * + * @return カラーバッファの幅 + */ + public getBufferWidth(): number { + return this._bufferWidth; + } + + /** + * カラーバッファの高さを取得する。 + * + * @return カラーバッファの高さ + */ + public getBufferHeight(): number { + return this._bufferHeight; + } + + /** + * オフスクリーンサーフェスが有効かどうかを確認する。 + * + * @return 有効な場合はtrue、無効な場合はfalse + */ + public isValid(): boolean { + return this._renderTexture != null; + } + + /** + * 以前のフレームバッファを取得する。 + * + * @return 以前のフレームバッファ + */ + public getOldFBO(): WebGLFramebuffer { + return this._oldFbo; + } + + /** + * コンストラクタ + */ + constructor() { + this._gl = null; + this._colorBuffer = null; + this._renderTexture = null; + this._bufferWidth = 0; + this._bufferHeight = 0; + this._oldFbo = null; + } + + protected _gl: WebGLRenderingContext | WebGL2RenderingContext; // WebGLのコンテキスト + protected _colorBuffer: WebGLTexture; // カラーバッファ + protected _renderTexture: WebGLFramebuffer; // フレームバッファ + protected _bufferWidth: number; // カラーバッファの幅 + protected _bufferHeight: number; // カラーバッファの高さ + private _oldFbo: WebGLFramebuffer; // 以前のフレームバッファ +} + +// Namespace definition for compatibility. +import * as $ from './cubismrendertarget_webgl'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismOffscreenSurface_WebGL = $.CubismRenderTarget_WebGL; + export type CubismOffscreenSurface_WebGL = $.CubismRenderTarget_WebGL; +} diff --git a/avatar-h5-renderer/framework/src/rendering/cubismshader_webgl.ts b/avatar-h5-renderer/framework/src/rendering/cubismshader_webgl.ts new file mode 100644 index 0000000..b3d605d --- /dev/null +++ b/avatar-h5-renderer/framework/src/rendering/cubismshader_webgl.ts @@ -0,0 +1,2063 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMatrix44 } from '../math/cubismmatrix44'; +import { + CubismColorBlend, + CubismModel, + CubismAlphaBlend +} from '../model/cubismmodel'; +import { csmRect } from '../type/csmrectf'; +import { CubismLogError, CubismLogWarning } from '../utils/cubismdebug'; +import { CubismRenderTarget_WebGL } from './cubismrendertarget_webgl'; +import { CubismOffscreenRenderTarget_WebGL } from './cubismoffscreenrendertarget_webgl'; +import { CubismBlendMode, CubismTextureColor } from './cubismrenderer'; +import { CubismRenderer_WebGL } from './cubismrenderer_webgl'; + +// Shader +const VertShaderSrcPath = 'vertshadersrc.vert'; +const VertShaderSrcMaskedPath = 'vertshadersrcmasked.vert'; +const VertShaderSrcSetupMaskPath = 'vertshadersrcsetupmask.vert'; +const FragShaderSrcSetupMaskPath = 'fragshadersrcsetupmask.frag'; +const FragShaderSrcPremultipliedAlphaPath = + 'fragshadersrcpremultipliedalpha.frag'; +const FragShaderSrcMaskPremultipliedAlphaPath = + 'fragshadersrcmaskpremultipliedalpha.frag'; +const FragShaderSrcMaskInvertedPremultipliedAlphaPath = + 'fragshadersrcmaskinvertedpremultipliedalpha.frag'; + +// Copy & Blend Shader +const VertShaderSrcCopyPath = 'vertshadersrccopy.vert'; +const FragShaderSrcCopyPath = 'fragshadersrccopy.frag'; +const FragShaderSrcColorBlendPath = 'fragshadersrccolorblend.frag'; +const FragShaderSrcAlphaBlendPath = 'fragshadersrcalphablend.frag'; +const VertShaderSrcBlendPath = 'vertshadersrcblend.vert'; +const FragShaderSrcBlendPath = 'fragshadersrcpremultipliedalphablend.frag'; + +// Blend mode Prefix +const ColorBlendPrefix = 'ColorBlend_'; +const AlphaBlendPrefix = 'AlphaBlend_'; + +let s_instance: CubismShaderManager_WebGL; // インスタンス(シングルトン) + +const s_renderTargetVertexArray: Float32Array = new Float32Array([ + -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0 +]); +const s_renderTargetUvArray: Float32Array = new Float32Array([ + 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0 +]); +const s_renderTargetReverseUvArray = new Float32Array([ + 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0 +]); + +/** + * WebGL用のシェーダープログラムを生成・破棄するクラス + */ +export class CubismShader_WebGL { + /** + * 非同期でシェーダーをパスから読み込む + * + * @param url シェーダーのURL + * + * @return シェーダーのソースコード + */ + private async loadShader(url: string): Promise { + const response = await fetch(url); + return await response.text(); + } + + /** + * ブレンドモード用のシェーダーを読み込む + */ + private async loadShaders(): Promise { + // _shaderPathがnullまたはundefinedの場合はデフォルトパスを使用 + const shaderDir = this._shaderPath ?? this._defaultShaderPath; + + // シェーダーファイルのパスとプロパティの対応 + // NOTE: prop は CubismShader_WebGL に設定された変数名 + const shaderFiles: { path: string; prop: keyof CubismShader_WebGL }[] = [ + { path: shaderDir + VertShaderSrcPath, prop: '_vertShaderSrc' }, + { + path: shaderDir + VertShaderSrcMaskedPath, + prop: '_vertShaderSrcMasked' + }, + { + path: shaderDir + VertShaderSrcSetupMaskPath, + prop: '_vertShaderSrcSetupMask' + }, + { + path: shaderDir + FragShaderSrcSetupMaskPath, + prop: '_fragShaderSrcSetupMask' + }, + { + path: shaderDir + FragShaderSrcPremultipliedAlphaPath, + prop: '_fragShaderSrcPremultipliedAlpha' + }, + { + path: shaderDir + FragShaderSrcMaskPremultipliedAlphaPath, + prop: '_fragShaderSrcMaskPremultipliedAlpha' + }, + { + path: shaderDir + FragShaderSrcMaskInvertedPremultipliedAlphaPath, + prop: '_fragShaderSrcMaskInvertedPremultipliedAlpha' + }, + + { path: shaderDir + VertShaderSrcCopyPath, prop: '_vertShaderSrcCopy' }, + { path: shaderDir + FragShaderSrcCopyPath, prop: '_fragShaderSrcCopy' }, + { + path: shaderDir + FragShaderSrcColorBlendPath, + prop: '_fragShaderSrcColorBlend' + }, + { + path: shaderDir + FragShaderSrcAlphaBlendPath, + prop: '_fragShaderSrcAlphaBlend' + }, + { path: shaderDir + VertShaderSrcBlendPath, prop: '_vertShaderSrcBlend' }, + { path: shaderDir + FragShaderSrcBlendPath, prop: '_fragShaderSrcBlend' } + ]; + + // シェーダーファイルを非同期で読み込み、結果をプロパティに設定 + const results = await Promise.all( + shaderFiles.map(file => + this.loadShader(file.path) + .then(data => ({ prop: file.prop, data })) + .catch(error => { + console.error(`Error loading ${file.path} shader:`, error); + return { prop: file.prop, data: '' }; + }) + ) + ); + + // 変数に内容を登録 + results.forEach(result => { + (this as any)[result.prop] = result.data; + }); + } + + /** + * コンストラクタ + */ + public constructor() { + this._shaderSets = new Array(); + this._isShaderLoading = false; + this._isShaderLoaded = false; + + // カラーブレンド用のマップ + this._colorBlendMap = new Map(); + this._colorBlendValues = new Array(); + + const colorBlendKeys = Object.keys(CubismColorBlend); + + // Object.values() のポリフィル + const colorBlendRawValues = Object.keys(CubismColorBlend).map( + k => CubismColorBlend[k as keyof typeof CubismColorBlend] + ); + + for (let i = 0; i < colorBlendKeys.length; i++) { + const colorBlendKey = colorBlendKeys[i]; + + if (colorBlendKey.includes(ColorBlendPrefix)) { + const blendModeName = colorBlendKey.slice(ColorBlendPrefix.length); + + const colorBlendNumber = parseInt(colorBlendRawValues[i].toString()); + + this._colorBlendMap.set(colorBlendNumber, blendModeName); + + this._colorBlendValues.push(colorBlendNumber); + } + } + + // アルファブレンド用のマップ + this._alphaBlendMap = new Map(); + this._alphaBlendValues = new Array(); + + const alphaBlendKeys = Object.keys(CubismAlphaBlend); + + // Object.values() のポリフィル + const alphaBlendRawValues = Object.keys(CubismAlphaBlend).map( + k => CubismAlphaBlend[k as keyof typeof CubismAlphaBlend] + ); + + for (let i = 0; i < alphaBlendKeys.length; i++) { + const alphaBlendKey = alphaBlendKeys[i]; + + if (alphaBlendKey.includes(AlphaBlendPrefix)) { + const blendModeName = alphaBlendKey.slice(AlphaBlendPrefix.length); + + const alphaBlendNumber = parseInt(alphaBlendRawValues[i].toString()); + + this._alphaBlendMap.set(alphaBlendNumber, blendModeName); + + this._alphaBlendValues.push(alphaBlendNumber); + } + } + + this._blendShaderSetMap = new Map(); + + this._shaderCount = + ShaderNames.ShaderNames_ShaderCount + + 1 + + (this._colorBlendValues.length - 3) * + (this._alphaBlendValues.length - 1) * + 3; + // シェーダーの数 = + // (マスク生成用 + (通常用 + 加算 + 乗算) * (マスク無の乗算済アルファ対応版 + マスク有の乗算済アルファ対応版 + マスク有反転の乗算済アルファ対応版)) + // + 1(コピー用のシェーダー) + // + カラーブレンドの数(後方互換とNone除く) * アルファブレンドの数(None除く) * (通常 + マスク + 反転マスク) + + this._defaultShaderPath = '../../Framework/Shaders/WebGL/'; + this._shaderPath = this._defaultShaderPath; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this.releaseShaderProgram(); + } + + /** + * 描画用のシェーダプログラムの一連のセットアップを実行する + * + * @param renderer レンダラー + * @param model 描画対象のモデル + * @param index 描画対象のメッシュのインデックス + */ + public setupShaderProgramForDrawable( + renderer: CubismRenderer_WebGL, + model: Readonly, + index: number + ): void { + if (!renderer.isPremultipliedAlpha()) { + CubismLogError('NoPremultipliedAlpha is not allowed'); + } + + if (this._shaderSets.length == 0) { + this.generateShaders(); + } + + if (this._isShaderLoaded == false) { + CubismLogWarning('Shader program is not initialized.'); + return; + } + + // Blending + let srcColor: number; + let dstColor: number; + let srcAlpha: number; + let dstAlpha: number; + + // _shaderSets用のオフセット計算 + const masked: boolean = + renderer.getClippingContextBufferForDrawable() != null; // この描画オブジェクトはマスク対象か + const invertedMask: boolean = model.getDrawableInvertedMaskBit(index); + const offset: number = masked ? (invertedMask ? 2 : 1) : 0; + + let shaderSet: CubismShaderSet; + // Cubism 5.2以前のシェーダを使用する場合はtrue + let isUsingCompatible: boolean = true; + + if (model.isBlendModeEnabled()) { + const colorBlendMode: CubismColorBlend = + model.getDrawableColorBlend(index); + const alphaBlendMode: CubismAlphaBlend = + model.getDrawableAlphaBlend(index); + + if ( + colorBlendMode == CubismColorBlend.ColorBlend_None || + alphaBlendMode == CubismAlphaBlend.AlphaBlend_None || + (colorBlendMode == CubismColorBlend.ColorBlend_Normal && + alphaBlendMode == CubismAlphaBlend.AlphaBlend_Over) + ) { + // Cubism 5.2以前のシェーダを使用する。 + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_NormalPremultipliedAlpha + offset + ]; + + srcColor = this.gl.ONE; + dstColor = this.gl.ONE_MINUS_SRC_ALPHA; + srcAlpha = this.gl.ONE; + dstAlpha = this.gl.ONE_MINUS_SRC_ALPHA; + } else { + switch (colorBlendMode) { + // Cubism 5.2以前のシェーダを使用する。 + case CubismColorBlend.ColorBlend_AddCompatible: + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_AddPremultipliedAlpha + offset + ]; + srcColor = this.gl.ONE; + dstColor = this.gl.ONE; + srcAlpha = this.gl.ZERO; + dstAlpha = this.gl.ONE; + break; + // Cubism 5.2以前のシェーダを使用する。 + case CubismColorBlend.ColorBlend_MultiplyCompatible: + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_MultPremultipliedAlpha + offset + ]; + srcColor = this.gl.DST_COLOR; + dstColor = this.gl.ONE_MINUS_SRC_ALPHA; + srcAlpha = this.gl.ZERO; + dstAlpha = this.gl.ONE; + break; + // ブレンドモードの組み合わせでシェーダーを決定 + default: + { + const srcBuffer = + renderer._currentOffscreen != null + ? renderer._currentOffscreen + : renderer.getModelRenderTarget(0); + + // 先にコピーを行う + CubismRenderTarget_WebGL.copyBuffer( + this.gl as WebGL2RenderingContext, + srcBuffer, + renderer.getModelRenderTarget(1) + ); + const baseShaderSetIndex = this._blendShaderSetMap.get( + this._colorBlendMap.get(colorBlendMode) + + this._alphaBlendMap.get(alphaBlendMode) + ); + shaderSet = this._shaderSets[baseShaderSetIndex + offset]; + srcColor = this.gl.ONE; + dstColor = this.gl.ZERO; + srcAlpha = this.gl.ONE; + dstAlpha = this.gl.ZERO; + isUsingCompatible = false; + } + break; + } + } + } else { + // Cubism 5.2以前のシェーダを使用する。 + switch (model.getDrawableBlendMode(index)) { + case CubismBlendMode.CubismBlendMode_Normal: + default: + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_NormalPremultipliedAlpha + offset + ]; + srcColor = this.gl.ONE; + dstColor = this.gl.ONE_MINUS_SRC_ALPHA; + srcAlpha = this.gl.ONE; + dstAlpha = this.gl.ONE_MINUS_SRC_ALPHA; + break; + + case CubismBlendMode.CubismBlendMode_Additive: + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_AddPremultipliedAlpha + offset + ]; + srcColor = this.gl.ONE; + dstColor = this.gl.ONE; + srcAlpha = this.gl.ZERO; + dstAlpha = this.gl.ONE; + break; + + case CubismBlendMode.CubismBlendMode_Multiplicative: + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_MultPremultipliedAlpha + offset + ]; + srcColor = this.gl.DST_COLOR; + dstColor = this.gl.ONE_MINUS_SRC_ALPHA; + srcAlpha = this.gl.ZERO; + dstAlpha = this.gl.ONE; + break; + } + } + + this.gl.useProgram(shaderSet.shaderProgram); + + // 頂点配列の設定 + if (renderer._bufferData.vertex == null) { + renderer._bufferData.vertex = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.vertex); + + // 頂点配列の設定 + const vertexArray: Float32Array = model.getDrawableVertices(index); + this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexArray, this.gl.DYNAMIC_DRAW); + this.gl.enableVertexAttribArray(shaderSet.attributePositionLocation); + this.gl.vertexAttribPointer( + shaderSet.attributePositionLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + // テクスチャ頂点の設定 + if (renderer._bufferData.uv == null) { + renderer._bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.uv); + const uvArray: Float32Array = model.getDrawableVertexUvs(index); + this.gl.bufferData(this.gl.ARRAY_BUFFER, uvArray, this.gl.DYNAMIC_DRAW); + this.gl.enableVertexAttribArray(shaderSet.attributeTexCoordLocation); + this.gl.vertexAttribPointer( + shaderSet.attributeTexCoordLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + if (masked) { + this.gl.activeTexture(this.gl.TEXTURE1); + + // frameBufferに書かれたテクスチャ + const tex: WebGLTexture = renderer + .getDrawableMaskBuffer( + renderer.getClippingContextBufferForDrawable()._bufferIndex + ) + .getColorBuffer(); + this.gl.bindTexture(this.gl.TEXTURE_2D, tex); + this.gl.uniform1i(shaderSet.samplerTexture1Location, 1); + + // view座標をClippingContextの座標に変換するための行列を設定 + this.gl.uniformMatrix4fv( + shaderSet.uniformClipMatrixLocation, + false, + renderer.getClippingContextBufferForDrawable()._matrixForDraw.getArray() + ); + + // 使用するカラーチャンネルを設定 + const channelIndex: number = + renderer.getClippingContextBufferForDrawable()._layoutChannelIndex; + const colorChannel: CubismTextureColor = renderer + .getClippingContextBufferForDrawable() + .getClippingManager() + .getChannelFlagAsColor(channelIndex); + this.gl.uniform4f( + shaderSet.uniformChannelFlagLocation, + colorChannel.r, + colorChannel.g, + colorChannel.b, + colorChannel.a + ); + + if (model.isBlendModeEnabled()) { + this.gl.uniform1f( + shaderSet.uniformInvertMaskFlagLocation, + invertedMask ? 1.0 : 0.0 + ); + } + } + + // テクスチャ設定 + const textureNo: number = model.getDrawableTextureIndex(index); + const textureId: WebGLTexture = renderer.getBindedTextures().get(textureNo); + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, textureId); + this.gl.uniform1i(shaderSet.samplerTexture0Location, 0); + + //座標変換 + const matrix4x4: CubismMatrix44 = renderer.getMvpMatrix(); + this.gl.uniformMatrix4fv( + shaderSet.uniformMatrixLocation, + false, + matrix4x4.getArray() + ); + + //ベース色の取得 + let baseColor: CubismTextureColor = null; + + if (model.isBlendModeEnabled()) { + // ブレンドモードではモデルカラーは最後に処理するため不透明度のみ対応させる + const drawableOpacity = model.getDrawableOpacity(index); + baseColor = new CubismTextureColor( + drawableOpacity, + drawableOpacity, + drawableOpacity, + drawableOpacity + ); + } else { + baseColor = renderer.getModelColorWithOpacity( + model.getDrawableOpacity(index) + ); + } + + const multiplyAndScreenColor = model.getOverrideMultiplyAndScreenColor(); + const multiplyColor: CubismTextureColor = + multiplyAndScreenColor.getDrawableMultiplyColor(index); + const screenColor: CubismTextureColor = + multiplyAndScreenColor.getDrawableScreenColor(index); + + this.gl.uniform4f( + shaderSet.uniformBaseColorLocation, + baseColor.r, + baseColor.g, + baseColor.b, + baseColor.a + ); + + this.gl.uniform4f( + shaderSet.uniformMultiplyColorLocation, + multiplyColor.r, + multiplyColor.g, + multiplyColor.b, + multiplyColor.a + ); + + this.gl.uniform4f( + shaderSet.uniformScreenColorLocation, + screenColor.r, + screenColor.g, + screenColor.b, + screenColor.a + ); + + // Cubism 5.3以降のシェーダを使用する場合 + if (model.isBlendModeEnabled()) { + this.gl.activeTexture(this.gl.TEXTURE2); + + // Cubism 5.2以前のシェーダを使用する場合は不要なのでこの処理をスキップ + if (!isUsingCompatible) { + const tex: WebGLTexture = renderer + .getModelRenderTarget(1) + .getColorBuffer(); + this.gl.bindTexture(this.gl.TEXTURE_2D, tex); + this.gl.uniform1i(shaderSet.samplerFrameBufferTextureLocation, 2); + } + } + + // IBOを作成し、データを転送 + if (renderer._bufferData.index == null) { + renderer._bufferData.index = this.gl.createBuffer(); + } + const indexArray: Uint16Array = model.getDrawableVertexIndices(index); + + this.gl.bindBuffer( + this.gl.ELEMENT_ARRAY_BUFFER, + renderer._bufferData.index + ); + this.gl.bufferData( + this.gl.ELEMENT_ARRAY_BUFFER, + indexArray, + this.gl.DYNAMIC_DRAW + ); + + this.gl.blendFuncSeparate(srcColor, dstColor, srcAlpha, dstAlpha); + } + + /** + * オフスクリーン用のシェーダプログラムの一連のセットアップを実行する + * + * @param renderer レンダラー + * @param model 描画対象のモデル + * @param offscreen 描画対象のオフスクリーン + */ + public setupShaderProgramForOffscreen( + renderer: CubismRenderer_WebGL, + model: Readonly, + offscreen: CubismOffscreenRenderTarget_WebGL + ): void { + if (!renderer.isPremultipliedAlpha()) { + CubismLogError('NoPremultipliedAlpha is not allowed'); + } + + if (this._shaderSets.length == 0) { + this.generateShaders(); + } + + if (this._isShaderLoaded == false) { + CubismLogWarning('Shader program is not initialized.'); + return; + } + + // Blending + let srcColor: number; + let dstColor: number; + let srcAlpha: number; + let dstAlpha: number; + + const offscreenIndex: number = offscreen.getOffscreenIndex(); + // _shaderSets用のオフセット計算 + const masked: boolean = + renderer.getClippingContextBufferForOffscreen() != null; // この描画オブジェクトはマスク対象か + const invertedMask: boolean = + model.getOffscreenInvertedMask(offscreenIndex); + const offset: number = masked ? (invertedMask ? 2 : 1) : 0; + + let shaderSet: CubismShaderSet; + // Cubism 5.2以前のシェーダを使用する場合はtrue + let isUsingCompatible: boolean = true; + + const colorBlendMode: CubismColorBlend = + model.getOffscreenColorBlend(offscreenIndex); + const alphaBlendMode: CubismAlphaBlend = + model.getOffscreenAlphaBlend(offscreenIndex); + + if ( + colorBlendMode == CubismColorBlend.ColorBlend_None || + alphaBlendMode == CubismAlphaBlend.AlphaBlend_None || + (colorBlendMode == CubismColorBlend.ColorBlend_Normal && + alphaBlendMode == CubismAlphaBlend.AlphaBlend_Over) + ) { + // Cubism 5.2以前のシェーダを使用する。 + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_NormalPremultipliedAlpha + offset + ]; + + srcColor = this.gl.ONE; + dstColor = this.gl.ONE_MINUS_SRC_ALPHA; + srcAlpha = this.gl.ONE; + dstAlpha = this.gl.ONE_MINUS_SRC_ALPHA; + } else { + switch (colorBlendMode as CubismColorBlend) { + // Cubism 5.2以前のシェーダを使用する。 + case CubismColorBlend.ColorBlend_AddCompatible: + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_AddPremultipliedAlpha + offset + ]; + srcColor = this.gl.ONE; + dstColor = this.gl.ONE; + srcAlpha = this.gl.ZERO; + dstAlpha = this.gl.ONE; + break; + case CubismColorBlend.ColorBlend_MultiplyCompatible: + shaderSet = + this._shaderSets[ + ShaderNames.ShaderNames_MultPremultipliedAlpha + offset + ]; + srcColor = this.gl.DST_COLOR; + dstColor = this.gl.ONE_MINUS_SRC_ALPHA; + srcAlpha = this.gl.ZERO; + dstAlpha = this.gl.ONE; + break; + default: + { + const srcBuffer = + offscreen.getOldOffscreen() != null + ? offscreen.getOldOffscreen() + : renderer.getModelRenderTarget(0); + + // 先にコピーを行う + CubismRenderTarget_WebGL.copyBuffer( + this.gl as WebGL2RenderingContext, + srcBuffer, + renderer.getModelRenderTarget(1) + ); + const baseShaderSetIndex = this._blendShaderSetMap.get( + this._colorBlendMap.get(colorBlendMode) + + this._alphaBlendMap.get(alphaBlendMode) + ); + shaderSet = this._shaderSets[baseShaderSetIndex + offset]; + srcColor = this.gl.ONE; + dstColor = this.gl.ZERO; + srcAlpha = this.gl.ONE; + dstAlpha = this.gl.ZERO; + isUsingCompatible = false; + } + break; + } + } + + this.gl.useProgram(shaderSet.shaderProgram); + + // 頂点配列の設定 + CubismRenderTarget_WebGL.copyBuffer( + this.gl as WebGL2RenderingContext, + offscreen, + renderer.getModelRenderTarget(2) + ); + this.gl.activeTexture(this.gl.TEXTURE0); + const tex0 = renderer.getModelRenderTarget(2).getColorBuffer(); + this.gl.bindTexture(this.gl.TEXTURE_2D, tex0); + this.gl.uniform1i(shaderSet.samplerTexture0Location, 0); + + //座標変換 + const matrix4x4: CubismMatrix44 = new CubismMatrix44(); + matrix4x4.loadIdentity(); + this.gl.uniformMatrix4fv( + shaderSet.uniformMatrixLocation, + false, + matrix4x4.getArray() + ); + + // ベース色の取得 + const offscreenOpacity = model.getOffscreenOpacity(offscreenIndex); + // 乗算済みアルファを使用するのでオフスクリーンの透明度を 1.0 に乗算した状態 + const baseColor: CubismTextureColor = new CubismTextureColor( + offscreenOpacity, + offscreenOpacity, + offscreenOpacity, + offscreenOpacity + ); + + const multiplyAndScreenColor = model.getOverrideMultiplyAndScreenColor(); + const multiplyColor: CubismTextureColor = + multiplyAndScreenColor.getOffscreenMultiplyColor(offscreenIndex); + const screenColor: CubismTextureColor = + multiplyAndScreenColor.getOffscreenScreenColor(offscreenIndex); + + this.gl.uniform4f( + shaderSet.uniformBaseColorLocation, + baseColor.r, + baseColor.g, + baseColor.b, + baseColor.a + ); + + this.gl.uniform4f( + shaderSet.uniformMultiplyColorLocation, + multiplyColor.r, + multiplyColor.g, + multiplyColor.b, + multiplyColor.a + ); + + this.gl.uniform4f( + shaderSet.uniformScreenColorLocation, + screenColor.r, + screenColor.g, + screenColor.b, + screenColor.a + ); + + this.gl.activeTexture(this.gl.TEXTURE2); + + // Cubism 5.2以前のシェーダを使用する場合は不要なのでこの処理をスキップ + if (!isUsingCompatible) { + const tex1: WebGLTexture = renderer + .getModelRenderTarget(1) + .getColorBuffer(); + this.gl.bindTexture(this.gl.TEXTURE_2D, tex1); + this.gl.uniform1i(shaderSet.samplerFrameBufferTextureLocation, 2); + } + + if (masked) { + this.gl.activeTexture(this.gl.TEXTURE1); + + // frameBufferに書かれたテクスチャ + const tex2: WebGLTexture = renderer + .getOffscreenMaskBuffer( + renderer.getClippingContextBufferForOffscreen()._bufferIndex + ) + .getColorBuffer(); + this.gl.bindTexture(this.gl.TEXTURE_2D, tex2); + this.gl.uniform1i(shaderSet.samplerTexture1Location, 1); + + // view座標をClippingContextの座標に変換するための行列を設定 + this.gl.uniformMatrix4fv( + shaderSet.uniformClipMatrixLocation, + false, + renderer + .getClippingContextBufferForOffscreen() + ._matrixForDraw.getArray() + ); + + // 使用するカラーチャンネルを設定 + const channelIndex: number = + renderer.getClippingContextBufferForOffscreen()._layoutChannelIndex; + const colorChannel: CubismTextureColor = renderer + .getClippingContextBufferForOffscreen() + .getClippingManager() + .getChannelFlagAsColor(channelIndex); + this.gl.uniform4f( + shaderSet.uniformChannelFlagLocation, + colorChannel.r, + colorChannel.g, + colorChannel.b, + colorChannel.a + ); + + if (model.isBlendModeEnabled()) { + this.gl.uniform1f( + shaderSet.uniformInvertMaskFlagLocation, + invertedMask ? 1.0 : 0.0 + ); + } + } + + // 頂点位置属性の設定 + if (!renderer._bufferData.vertex) { + renderer._bufferData.vertex = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.vertex); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + s_renderTargetVertexArray, + this.gl.STATIC_DRAW + ); + this.gl.enableVertexAttribArray(shaderSet.attributePositionLocation); + this.gl.vertexAttribPointer( + shaderSet.attributePositionLocation, + 2, + this.gl.FLOAT, + false, + Float32Array.BYTES_PER_ELEMENT * 2, + 0 + ); + + // テクスチャ座標属性の設定 + if (!renderer._bufferData.uv) { + renderer._bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.uv); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + s_renderTargetReverseUvArray, + this.gl.STATIC_DRAW + ); + this.gl.enableVertexAttribArray(shaderSet.attributeTexCoordLocation); + this.gl.vertexAttribPointer( + shaderSet.attributeTexCoordLocation, + 2, + this.gl.FLOAT, + false, + Float32Array.BYTES_PER_ELEMENT * 2, + 0 + ); + + this.gl.blendFuncSeparate(srcColor, dstColor, srcAlpha, dstAlpha); + } + + /** + * マスク用のシェーダプログラムの一連のセットアップを実行する + * + * @param renderer レンダラー + * @param model 描画対象のモデル + * @param index 描画対象のメッシュのインデックス + */ + public setupShaderProgramForMask( + renderer: CubismRenderer_WebGL, + model: Readonly, + index: number + ): void { + if (!renderer.isPremultipliedAlpha()) { + CubismLogError('NoPremultipliedAlpha is not allowed'); + } + + if (this._shaderSets.length == 0) { + this.generateShaders(); + } + + if (this._isShaderLoaded == false) { + CubismLogWarning('Shader program is not initialized.'); + return; + } + + const shaderSet: CubismShaderSet = + this._shaderSets[ShaderNames.ShaderNames_SetupMask]; + this.gl.useProgram(shaderSet.shaderProgram); + + // 頂点配列の設定 + if (renderer._bufferData.vertex == null) { + renderer._bufferData.vertex = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.vertex); + const vertexArray: Float32Array = model.getDrawableVertices(index); + this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexArray, this.gl.DYNAMIC_DRAW); + this.gl.enableVertexAttribArray(shaderSet.attributePositionLocation); + this.gl.vertexAttribPointer( + shaderSet.attributePositionLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + //テクスチャ設定 + if (renderer._bufferData.uv == null) { + renderer._bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.uv); + const textureNo: number = model.getDrawableTextureIndex(index); + const textureId: WebGLTexture = renderer.getBindedTextures().get(textureNo); + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, textureId); + this.gl.uniform1i(shaderSet.samplerTexture0Location, 0); + + // テクスチャ頂点の設定 + if (renderer._bufferData.uv == null) { + renderer._bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.uv); + const uvArray: Float32Array = model.getDrawableVertexUvs(index); + this.gl.bufferData(this.gl.ARRAY_BUFFER, uvArray, this.gl.DYNAMIC_DRAW); + this.gl.enableVertexAttribArray(shaderSet.attributeTexCoordLocation); + this.gl.vertexAttribPointer( + shaderSet.attributeTexCoordLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + // チャンネル + const channelIndex: number = + renderer.getClippingContextBufferForMask()._layoutChannelIndex; + const colorChannel: CubismTextureColor = renderer + .getClippingContextBufferForMask() + .getClippingManager() + .getChannelFlagAsColor(channelIndex); + this.gl.uniform4f( + shaderSet.uniformChannelFlagLocation, + colorChannel.r, + colorChannel.g, + colorChannel.b, + colorChannel.a + ); + + this.gl.uniformMatrix4fv( + shaderSet.uniformClipMatrixLocation, + false, + renderer.getClippingContextBufferForMask()._matrixForMask.getArray() + ); + + const rect: csmRect = + renderer.getClippingContextBufferForMask()._layoutBounds; + + this.gl.uniform4f( + shaderSet.uniformBaseColorLocation, + rect.x * 2.0 - 1.0, + rect.y * 2.0 - 1.0, + rect.getRight() * 2.0 - 1.0, + rect.getBottom() * 2.0 - 1.0 + ); + + // Blending + const srcColor: number = this.gl.ZERO; + const dstColor: number = this.gl.ONE_MINUS_SRC_COLOR; + const srcAlpha: number = this.gl.ZERO; + const dstAlpha: number = this.gl.ONE_MINUS_SRC_ALPHA; + + // IBOを作成し、データを転送 + if (renderer._bufferData.index == null) { + renderer._bufferData.index = this.gl.createBuffer(); + } + const indexArray: Uint16Array = model.getDrawableVertexIndices(index); + + this.gl.bindBuffer( + this.gl.ELEMENT_ARRAY_BUFFER, + renderer._bufferData.index + ); + this.gl.bufferData( + this.gl.ELEMENT_ARRAY_BUFFER, + indexArray, + this.gl.DYNAMIC_DRAW + ); + + this.gl.blendFuncSeparate(srcColor, dstColor, srcAlpha, dstAlpha); + } + + /** + * オフスクリーンのレンダリングターゲット用のシェーダープログラムを設定する + * + * @param renderer レンダラー + */ + public setupShaderProgramForOffscreenRenderTarget( + renderer: CubismRenderer_WebGL + ): void { + if (this._shaderSets.length == 0) { + this.generateShaders(); + } + + if (this._isShaderLoaded == false) { + CubismLogWarning('Shader program is not initialized.'); + return; + } + + // この時点のテクスチャはPMAになっているはずなので計算を行う + const baseColor = renderer.getModelColor(); + baseColor.r *= baseColor.a; + baseColor.g *= baseColor.a; + baseColor.b *= baseColor.a; + this.copyTexture(renderer, baseColor); + } + + /** + * オフスクリーンのレンダリングターゲットの内容をコピーする + * + * @param renderer レンダラー + * @param baseColor ベースカラー + */ + public copyTexture( + renderer: CubismRenderer_WebGL, + baseColor: CubismTextureColor + ) { + // Blending + const srcColor = this.gl.ONE; + const dstColor = this.gl.ONE_MINUS_SRC_ALPHA; + const srcAlpha = this.gl.ONE; + const dstAlpha = this.gl.ONE_MINUS_SRC_ALPHA; + + const shaderSet = this._shaderSets[10]; // ShaderNames_Copy = 10 + + this.gl.useProgram(shaderSet.shaderProgram); + + this.gl.uniform4f( + shaderSet.uniformBaseColorLocation, + baseColor.r, + baseColor.g, + baseColor.b, + baseColor.a + ); + + // オフスクリーンの内容を設定 + this.gl.activeTexture(this.gl.TEXTURE0); + const tex = renderer.getModelRenderTarget(0).getColorBuffer(); + this.gl.bindTexture(this.gl.TEXTURE_2D, tex); + this.gl.uniform1i(shaderSet.samplerTexture0Location, 0); + + // 頂点位置属性の設定 + if (!renderer._bufferData.vertex) { + renderer._bufferData.vertex = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.vertex); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + s_renderTargetVertexArray, + this.gl.STATIC_DRAW + ); + this.gl.enableVertexAttribArray(shaderSet.attributePositionLocation); + this.gl.vertexAttribPointer( + shaderSet.attributePositionLocation, + 2, + this.gl.FLOAT, + false, + Float32Array.BYTES_PER_ELEMENT * 2, + 0 + ); + + // テクスチャ座標属性の設定 + if (!renderer._bufferData.uv) { + renderer._bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, renderer._bufferData.uv); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + s_renderTargetUvArray, + this.gl.STATIC_DRAW + ); + this.gl.enableVertexAttribArray(shaderSet.attributeTexCoordLocation); + this.gl.vertexAttribPointer( + shaderSet.attributeTexCoordLocation, + 2, + this.gl.FLOAT, + false, + Float32Array.BYTES_PER_ELEMENT * 2, + 0 + ); + + this.gl.blendFuncSeparate(srcColor, dstColor, srcAlpha, dstAlpha); + } + + /** + * シェーダープログラムを解放する + */ + public releaseShaderProgram(): void { + for (let i = 0; i < this._shaderSets.length; i++) { + this.gl.deleteProgram(this._shaderSets[i].shaderProgram); + this._shaderSets[i].shaderProgram = 0; + this._shaderSets[i] = void 0; + this._shaderSets[i] = null; + } + } + + /** + * シェーダープログラムを初期化する + * + * @param vertShaderSrc 頂点シェーダのソース + * @param fragShaderSrc フラグメントシェーダのソース + */ + public generateShaders(): void { + if (this._isShaderLoading) { + return; + } + this._isShaderLoading = true; + this._isShaderLoaded = false; + this._shaderSets.length = this._shaderCount; + for (let i = 0; i < this._shaderCount; i++) { + this._shaderSets[i] = new CubismShaderSet(); + } + + // シェーダーのソースの読み込み + this.loadShaders() + .then(() => { + // NOTE: ファイルの読み込みを待つ必要があるためこのようにする + this.registerShader(); // 通常シェーダーの登録 + this.registerBlendShader(); // ブレンドモードシェーダーの登録 + this._isShaderLoading = false; + this._isShaderLoaded = true; + }) + .catch(error => { + this._isShaderLoading = false; + console.error('Failed to load shaders:', error); + }); + } + + /** + * シェーダープログラムを登録する + */ + public registerShader(): void { + const vertexShaderSrc = this._vertShaderSrc; + const vertexShaderSrcMasked = this._vertShaderSrcMasked; + const vertexShaderSrcSetupMask = this._vertShaderSrcSetupMask; + const fragmentShaderSrcSetupMask = this._fragShaderSrcSetupMask; + const fragmentShaderSrcPremultipliedAlpha = + this._fragShaderSrcPremultipliedAlpha; + const fragmentShaderSrcMaskPremultipliedAlpha = + this._fragShaderSrcMaskPremultipliedAlpha; + const fragmentShaderSrcMaskInvertedPremultipliedAlpha = + this._fragShaderSrcMaskInvertedPremultipliedAlpha; + + this._shaderSets[0].shaderProgram = this.loadShaderProgram( + vertexShaderSrcSetupMask, + fragmentShaderSrcSetupMask + ); + this._shaderSets[1].shaderProgram = this.loadShaderProgram( + vertexShaderSrc, + fragmentShaderSrcPremultipliedAlpha + ); + this._shaderSets[2].shaderProgram = this.loadShaderProgram( + vertexShaderSrcMasked, + fragmentShaderSrcMaskPremultipliedAlpha + ); + this._shaderSets[3].shaderProgram = this.loadShaderProgram( + vertexShaderSrcMasked, + fragmentShaderSrcMaskInvertedPremultipliedAlpha + ); + + // 加算も通常と同じシェーダーを利用する + this._shaderSets[4].shaderProgram = this._shaderSets[1].shaderProgram; + this._shaderSets[5].shaderProgram = this._shaderSets[2].shaderProgram; + this._shaderSets[6].shaderProgram = this._shaderSets[3].shaderProgram; + + // 乗算も通常と同じシェーダーを利用する + this._shaderSets[7].shaderProgram = this._shaderSets[1].shaderProgram; + this._shaderSets[8].shaderProgram = this._shaderSets[2].shaderProgram; + this._shaderSets[9].shaderProgram = this._shaderSets[3].shaderProgram; + + // SetupMask + this._shaderSets[0].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[0].shaderProgram, + 'a_position' + ); + this._shaderSets[0].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[0].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[0].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[0].shaderProgram, + 's_texture0' + ); + this._shaderSets[0].uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[0].shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets[0].uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets[0].shaderProgram, + 'u_channelFlag' + ); + this._shaderSets[0].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[0].shaderProgram, + 'u_baseColor' + ); + + // 通常(PremultipliedAlpha) + this._shaderSets[1].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[1].shaderProgram, + 'a_position' + ); + this._shaderSets[1].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[1].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[1].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[1].shaderProgram, + 's_texture0' + ); + this._shaderSets[1].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[1].shaderProgram, + 'u_matrix' + ); + this._shaderSets[1].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[1].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[1].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[1].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[1].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[1].shaderProgram, + 'u_screenColor' + ); + + // 通常(クリッピング、PremultipliedAlpha) + this._shaderSets[2].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[2].shaderProgram, + 'a_position' + ); + this._shaderSets[2].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[2].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[2].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[2].shaderProgram, + 's_texture0' + ); + this._shaderSets[2].samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets[2].shaderProgram, + 's_texture1' + ); + this._shaderSets[2].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[2].shaderProgram, + 'u_matrix' + ); + this._shaderSets[2].uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[2].shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets[2].uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets[2].shaderProgram, + 'u_channelFlag' + ); + this._shaderSets[2].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[2].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[2].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[2].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[2].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[2].shaderProgram, + 'u_screenColor' + ); + + // 通常(クリッピング・反転, PremultipliedAlpha) + this._shaderSets[3].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[3].shaderProgram, + 'a_position' + ); + this._shaderSets[3].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[3].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[3].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[3].shaderProgram, + 's_texture0' + ); + this._shaderSets[3].samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets[3].shaderProgram, + 's_texture1' + ); + this._shaderSets[3].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[3].shaderProgram, + 'u_matrix' + ); + this._shaderSets[3].uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[3].shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets[3].uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets[3].shaderProgram, + 'u_channelFlag' + ); + this._shaderSets[3].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[3].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[3].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[3].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[3].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[3].shaderProgram, + 'u_screenColor' + ); + + // 加算(PremultipliedAlpha) + this._shaderSets[4].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[4].shaderProgram, + 'a_position' + ); + this._shaderSets[4].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[4].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[4].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[4].shaderProgram, + 's_texture0' + ); + this._shaderSets[4].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[4].shaderProgram, + 'u_matrix' + ); + this._shaderSets[4].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[4].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[4].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[4].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[4].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[4].shaderProgram, + 'u_screenColor' + ); + + // 加算(クリッピング、PremultipliedAlpha) + this._shaderSets[5].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[5].shaderProgram, + 'a_position' + ); + this._shaderSets[5].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[5].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[5].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[5].shaderProgram, + 's_texture0' + ); + this._shaderSets[5].samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets[5].shaderProgram, + 's_texture1' + ); + this._shaderSets[5].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[5].shaderProgram, + 'u_matrix' + ); + this._shaderSets[5].uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[5].shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets[5].uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets[5].shaderProgram, + 'u_channelFlag' + ); + this._shaderSets[5].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[5].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[5].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[5].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[5].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[5].shaderProgram, + 'u_screenColor' + ); + + // 加算(クリッピング・反転、PremultipliedAlpha) + this._shaderSets[6].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[6].shaderProgram, + 'a_position' + ); + this._shaderSets[6].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[6].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[6].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[6].shaderProgram, + 's_texture0' + ); + this._shaderSets[6].samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets[6].shaderProgram, + 's_texture1' + ); + this._shaderSets[6].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[6].shaderProgram, + 'u_matrix' + ); + this._shaderSets[6].uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[6].shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets[6].uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets[6].shaderProgram, + 'u_channelFlag' + ); + this._shaderSets[6].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[6].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[6].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[6].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[6].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[6].shaderProgram, + 'u_screenColor' + ); + + // 乗算(PremultipliedAlpha) + this._shaderSets[7].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[7].shaderProgram, + 'a_position' + ); + this._shaderSets[7].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[7].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[7].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[7].shaderProgram, + 's_texture0' + ); + this._shaderSets[7].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[7].shaderProgram, + 'u_matrix' + ); + this._shaderSets[7].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[7].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[7].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[7].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[7].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[7].shaderProgram, + 'u_screenColor' + ); + + // 乗算(クリッピング、PremultipliedAlpha) + this._shaderSets[8].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[8].shaderProgram, + 'a_position' + ); + this._shaderSets[8].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[8].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[8].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[8].shaderProgram, + 's_texture0' + ); + this._shaderSets[8].samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets[8].shaderProgram, + 's_texture1' + ); + this._shaderSets[8].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[8].shaderProgram, + 'u_matrix' + ); + this._shaderSets[8].uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[8].shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets[8].uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets[8].shaderProgram, + 'u_channelFlag' + ); + this._shaderSets[8].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[8].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[8].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[8].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[8].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[8].shaderProgram, + 'u_screenColor' + ); + + // 乗算(クリッピング・反転、PremultipliedAlpha) + this._shaderSets[9].attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets[9].shaderProgram, + 'a_position' + ); + this._shaderSets[9].attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets[9].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[9].samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets[9].shaderProgram, + 's_texture0' + ); + this._shaderSets[9].samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets[9].shaderProgram, + 's_texture1' + ); + this._shaderSets[9].uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[9].shaderProgram, + 'u_matrix' + ); + this._shaderSets[9].uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets[9].shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets[9].uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets[9].shaderProgram, + 'u_channelFlag' + ); + this._shaderSets[9].uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets[9].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[9].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[9].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[9].uniformScreenColorLocation = this.gl.getUniformLocation( + this._shaderSets[9].shaderProgram, + 'u_screenColor' + ); + } + + /** + * ブレンドモード用のシェーダープログラムを登録する + */ + public registerBlendShader(): void { + // コピー用シェーダーの設定 + const vertShaderSrcCopy = this._vertShaderSrcCopy; + const fragShaderSrcCopy = this._fragShaderSrcCopy; + + const copyShaderSet = this._shaderSets[10]; // ShaderNames.Copy = 10 + copyShaderSet.shaderProgram = this.loadShaderProgram( + vertShaderSrcCopy, + fragShaderSrcCopy + ); + copyShaderSet.attributeTexCoordLocation = this.gl.getAttribLocation( + copyShaderSet.shaderProgram, + 'a_texCoord' + ); + copyShaderSet.attributePositionLocation = this.gl.getAttribLocation( + copyShaderSet.shaderProgram, + 'a_position' + ); + copyShaderSet.uniformBaseColorLocation = this.gl.getUniformLocation( + copyShaderSet.shaderProgram, + 'u_baseColor' + ); + + let shaderSetIndex = 11; + // ブレンドモード用シェーダーの設定 + for ( + let colorBlendIndex = 0; + colorBlendIndex < this._colorBlendValues.length; + colorBlendIndex++ + ) { + // NONEと後方互換はスキップ + if ( + this._colorBlendValues[colorBlendIndex] == + CubismColorBlend.ColorBlend_None || + this._colorBlendValues[colorBlendIndex] == + CubismColorBlend.ColorBlend_AddCompatible || + this._colorBlendValues[colorBlendIndex] == + CubismColorBlend.ColorBlend_MultiplyCompatible + ) { + continue; + } + + // カラーブレンド用のマクロ + const colorBlendValue = this._colorBlendValues[colorBlendIndex]; + const colorBlendName = this._colorBlendMap + .get(colorBlendValue) + .toUpperCase(); + const colorBlendMacro = `#define COLOR_BLEND_${colorBlendName}\n`; + + for ( + let alphablendIndex = 0; + alphablendIndex < this._alphaBlendValues.length; + alphablendIndex++ + ) { + // NONEと、カラーブレンド「Normal」かつアルファブレンド「Over」はスキップ + if ( + this._alphaBlendValues[alphablendIndex] == + CubismAlphaBlend.AlphaBlend_None || + (this._colorBlendValues[colorBlendIndex] == + CubismColorBlend.ColorBlend_Normal && + this._alphaBlendValues[alphablendIndex] == + CubismAlphaBlend.AlphaBlend_Over) + ) { + continue; + } + + // アルファブレンド用のマクロ + const alphaBlendValue = this._alphaBlendValues[alphablendIndex]; + const alphaBlendName = this._alphaBlendMap + .get(alphaBlendValue) + .toUpperCase(); + const alphaBlendMacro = `#define ALPHA_BLEND_${alphaBlendName}\n`; + + // シェーダーのソースを生成 + this.generateBlendShader( + colorBlendMacro, + alphaBlendMacro, + shaderSetIndex + ); + + this._blendShaderSetMap.set( + this._colorBlendMap.get(this._colorBlendValues[colorBlendIndex]) + + this._alphaBlendMap.get(this._alphaBlendValues[alphablendIndex]), + shaderSetIndex + ); + + // 1つの組み合わせが終わるこのタイミングでシェーダーのインデックスを更新 + shaderSetIndex += ShaderType.ShaderType_Count; + } + } + } + + /** + * ブレンドモード用のシェーダープログラムを生成する + * + * @param colorBlendMacro カラーブレンド用のマクロ + * @param alphaBlendMacro アルファブレンド用のマクロ + * @param shaderSetBaseIndex _shaderSets のインデックス + */ + private generateBlendShader( + colorBlendMacro: string, + alphaBlendMacro: string, + shaderSetBaseIndex: number + ): void { + for ( + let shaderTypeIndex: ShaderType = 0; + shaderTypeIndex < ShaderType.ShaderType_Count; + shaderTypeIndex++ + ) { + // ループごとにシェーダーのソースを初期化 + let vertexShaderSrc: string = ''; + let fragmentShaderStr: string = 'precision mediump float;\n'; + + // シェーダの種類が変わるたびにインデックスを変更 + const shaderSetIndex = shaderSetBaseIndex + shaderTypeIndex; + + // マクロの定義 + fragmentShaderStr += colorBlendMacro; + fragmentShaderStr += alphaBlendMacro; + + // ブレンドモードの種類に応じたマクロの定義 + fragmentShaderStr += this._fragShaderSrcColorBlend; + fragmentShaderStr += this._fragShaderSrcAlphaBlend; + + // シェーダの種類に応じたマクロの定義 + if ( + shaderTypeIndex == ShaderType.ShaderType_Masked || + shaderTypeIndex == ShaderType.ShaderType_MaskedInverted + ) { + const clippingMaskMacro = '#define CLIPPING_MASK\n'; + vertexShaderSrc += clippingMaskMacro; + fragmentShaderStr += clippingMaskMacro; + } + + // シェーダの本体のソースをファイルから読み込み + vertexShaderSrc += this._vertShaderSrcBlend; + fragmentShaderStr += this._fragShaderSrcBlend; + + // シェーダープログラムの生成 + this._shaderSets[shaderSetIndex].shaderProgram = this.loadShaderProgram( + vertexShaderSrc, + fragmentShaderStr + ); + + // シェーダープログラムへの変数のリンク + this._shaderSets[shaderSetIndex].attributePositionLocation = + this.gl.getAttribLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'a_position' + ); + this._shaderSets[shaderSetIndex].attributeTexCoordLocation = + this.gl.getAttribLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'a_texCoord' + ); + this._shaderSets[shaderSetIndex].samplerTexture0Location = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 's_texture0' + ); + this._shaderSets[shaderSetIndex].uniformMatrixLocation = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'u_matrix' + ); + this._shaderSets[shaderSetIndex].uniformBaseColorLocation = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'u_baseColor' + ); + this._shaderSets[shaderSetIndex].uniformMultiplyColorLocation = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'u_multiplyColor' + ); + this._shaderSets[shaderSetIndex].uniformScreenColorLocation = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'u_screenColor' + ); + + // ブレンドモード用のテクスチャ + this._shaderSets[shaderSetIndex].samplerFrameBufferTextureLocation = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 's_blendTexture' + ); + + // クリップ対象の場合 + if ( + shaderTypeIndex == ShaderType.ShaderType_Masked || + shaderTypeIndex == ShaderType.ShaderType_MaskedInverted + ) { + // マスク用テクスチャ + this._shaderSets[shaderSetIndex].samplerTexture1Location = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 's_texture1' + ); + + // クリップ用の行列 + this._shaderSets[shaderSetIndex].uniformClipMatrixLocation = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'u_clipMatrix' + ); + + // チャンネルフラグ + this._shaderSets[shaderSetIndex].uniformChannelFlagLocation = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'u_channelFlag' + ); + + // 反転マスク用の値(反転なら 1.0 が代入される) + this._shaderSets[shaderSetIndex].uniformInvertMaskFlagLocation = + this.gl.getUniformLocation( + this._shaderSets[shaderSetIndex].shaderProgram, + 'u_invertClippingMask' + ); + } + } + } + + /** + * シェーダプログラムをロードしてアドレスを返す + * + * @param vertexShaderSource 頂点シェーダのソース + * @param fragmentShaderSource フラグメントシェーダのソース + * + * @return シェーダプログラムのアドレス + */ + public loadShaderProgram( + vertexShaderSource: string, + fragmentShaderSource: string + ): WebGLProgram { + // Create Shader Program + let shaderProgram: WebGLProgram = this.gl.createProgram(); + + let vertShader = this.compileShaderSource( + this.gl.VERTEX_SHADER, + vertexShaderSource + ); + + if (!vertShader) { + CubismLogError('Vertex shader compile error!'); + return 0; + } + + let fragShader = this.compileShaderSource( + this.gl.FRAGMENT_SHADER, + fragmentShaderSource + ); + if (!fragShader) { + CubismLogError('Fragment shader compile error!'); + return 0; + } + + // Attach vertex shader to program + this.gl.attachShader(shaderProgram, vertShader); + + // Attach fragment shader to program + this.gl.attachShader(shaderProgram, fragShader); + + // link program + this.gl.linkProgram(shaderProgram); + const linkStatus = this.gl.getProgramParameter( + shaderProgram, + this.gl.LINK_STATUS + ); + + // リンクに失敗したらシェーダーを削除 + if (!linkStatus) { + CubismLogError('Failed to link program: {0}', shaderProgram); + + this.gl.deleteShader(vertShader); + vertShader = 0; + + this.gl.deleteShader(fragShader); + fragShader = 0; + + if (shaderProgram) { + this.gl.deleteProgram(shaderProgram); + shaderProgram = 0; + } + + return 0; + } + + // Release vertex and fragment shaders. + this.gl.deleteShader(vertShader); + this.gl.deleteShader(fragShader); + + return shaderProgram; + } + + /** + * シェーダープログラムをコンパイルする + * + * @param shaderType シェーダタイプ(Vertex/Fragment) + * @param shaderSource シェーダソースコード + * + * @return コンパイルされたシェーダープログラム + */ + public compileShaderSource( + shaderType: GLenum, + shaderSource: string + ): WebGLProgram { + const source: string = shaderSource; + + const shader: WebGLProgram = this.gl.createShader(shaderType); + this.gl.shaderSource(shader, source); + this.gl.compileShader(shader); + + if (!shader) { + const log: string = this.gl.getShaderInfoLog(shader); + CubismLogError('Shader compile log: {0} ', log); + } + + const status: any = this.gl.getShaderParameter( + shader, + this.gl.COMPILE_STATUS + ); + if (!status) { + const log: string = this.gl.getShaderInfoLog(shader); + CubismLogError('Shader compile log: {0} ', log); + this.gl.deleteShader(shader); + return null; + } + + return shader; + } + + /** + * WebGLレンダリングコンテキストを設定する + * + * @param gl WebGLレンダリングコンテキスト + */ + public setGl(gl: WebGLRenderingContext | WebGL2RenderingContext): void { + this.gl = gl; + } + + /** + * ブレンドモード用のシェーダーパスを設定する + * + * @param shaderPath シェーダーパス + */ + public setShaderPath(shaderPath: string): void { + this._shaderPath = shaderPath; + } + + /** + * シェーダーパスを取得する + * + * @return シェーダーパス + */ + public getShaderPath(): string { + return this._shaderPath; + } + + _shaderSets: Array; // ロードしたシェーダープログラムを保持する変数 + gl: WebGLRenderingContext | WebGL2RenderingContext; // webglコンテキスト + + _colorBlendMap: Map; // カラーブレンドの値と名称を紐づけする変数 + _alphaBlendMap: Map; // アルファブレンドの値と名称を紐づけする変数 + + _colorBlendValues: Array; // カラーブレンドの値を保持する変数 + _alphaBlendValues: Array; // アルファブレンドの値を保持する変数 + + _blendShaderSetMap: Map; // ブレンドモード用のシェーダーの名称とインデックスを紐づけする変数 + + _shaderCount: number; // シェーダープログラムの数 + + _vertShaderSrc: string; // 頂点シェーダーのソース + _vertShaderSrcMasked: string; // マスク用の頂点シェーダーのソース + _vertShaderSrcSetupMask: string; // マスク用の頂点シェーダーのソース + _fragShaderSrcSetupMask: string; // マスク用のフラグメントシェーダーのソース + _fragShaderSrcPremultipliedAlpha: string; // プレマルチプライドアルファ用のフラグメントシェーダーのソース + _fragShaderSrcMaskPremultipliedAlpha: string; // マスク用プレマルチプライドアルファのフラグメントシェーダーのソース + _fragShaderSrcMaskInvertedPremultipliedAlpha: string; // 反転マスク用プレマルチプライドアルファのフラグメントシェーダーのソース + + _vertShaderSrcCopy: string; // 頂点シェーダーのソース + _fragShaderSrcCopy: string; // コピー用のフラグメントシェーダーのソース + + _fragShaderSrcColorBlend: string; // ブレンドモード用のシェーダーのソース + _fragShaderSrcAlphaBlend: string; // アルファブレンド用のシェーダーのソース + _vertShaderSrcBlend: string; // 頂点シェーダーのソース + _fragShaderSrcBlend: string; // フラグメントシェーダーのソース + _isShaderLoading: boolean; // シェーダーの読み込み中かどうか + _isShaderLoaded: boolean; // シェーダーの読み込みが完了したかどうか + _defaultShaderPath: string; // デフォルトのシェーダーパス + _shaderPath: string; // シェーダーパス +} + +/** + * GLContextごとにCubismShader_WebGLを確保するためのクラス + * シングルトンなクラスであり、CubismShaderManager_WebGL.getInstanceからアクセスする。 + */ +export class CubismShaderManager_WebGL { + /** + * インスタンスを取得する(シングルトン) + * + * @return インスタンス + */ + public static getInstance(): CubismShaderManager_WebGL { + if (s_instance == null) { + s_instance = new CubismShaderManager_WebGL(); + } + return s_instance; + } + + /** + * インスタンスを開放する(シングルトン) + */ + public static deleteInstance(): void { + if (s_instance) { + s_instance.release(); + s_instance = null; + } + } + + /** + * Privateなコンストラクタ + */ + private constructor() { + this._shaderMap = new Map(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for (const item of this._shaderMap) { + item[1].release(); + } + this._shaderMap.clear(); + } + + /** + * GLContextをキーにShaderを取得する + * + * @param gl glコンテキスト + * + * @return shaderを返す + */ + public getShader(gl: WebGLRenderingContext): CubismShader_WebGL { + return this._shaderMap.get(gl); + } + + /** + * GLContextを登録する + * + * @param gl glコンテキスト + */ + public setGlContext(gl: WebGLRenderingContext): void { + if (!this._shaderMap.has(gl)) { + const instance = new CubismShader_WebGL(); + instance.setGl(gl); + this._shaderMap.set(gl, instance); + } + } + + /** + * GLContextごとのShaderを保持する変数 + */ + private _shaderMap: Map; +} + +/** + * CubismShader_WebGLのインナークラス + */ +export class CubismShaderSet { + shaderProgram: WebGLProgram; // シェーダープログラムのアドレス + attributePositionLocation: GLuint; // シェーダープログラムに渡す変数のアドレス(Position) + attributeTexCoordLocation: GLuint; // シェーダープログラムに渡す変数のアドレス(TexCoord) + uniformMatrixLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Matrix) + uniformClipMatrixLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(ClipMatrix) + samplerTexture0Location: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Texture0) + samplerTexture1Location: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Texture1) + uniformBaseColorLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(BaseColor) + uniformChannelFlagLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(ChannelFlag) + uniformMultiplyColorLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(MultiplyColor) + uniformScreenColorLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(ScreenColor) + samplerFrameBufferTextureLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(BlendTexture) + uniformInvertMaskFlagLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(InvertMask) +} + +/** + * シェーダーの名前を定義する列挙型 + */ +export enum ShaderNames { + // SetupMask + ShaderNames_SetupMask, + + // Normal + ShaderNames_NormalPremultipliedAlpha, + ShaderNames_NormalMaskedPremultipliedAlpha, + ShaderNames_NomralMaskedInvertedPremultipliedAlpha, + + // Add + ShaderNames_AddPremultipliedAlpha, + ShaderNames_AddMaskedPremultipliedAlpha, + ShaderNames_AddMaskedPremultipliedAlphaInverted, + + // Mult + ShaderNames_MultPremultipliedAlpha, + ShaderNames_MultMaskedPremultipliedAlpha, + ShaderNames_MultMaskedPremultipliedAlphaInverted, + + // ShaderCount + ShaderNames_ShaderCount +} + +/** + * シェーダーの種類を定義する列挙型 + */ +export enum ShaderType { + ShaderType_Normal = 0, + ShaderType_Masked = 1, + ShaderType_MaskedInverted = 2, + ShaderType_Count +} + +// Namespace definition for compatibility. +import * as $ from './cubismshader_webgl'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismShaderSet = $.CubismShaderSet; + export type CubismShaderSet = $.CubismShaderSet; + export const CubismShader_WebGL = $.CubismShader_WebGL; + export type CubismShader_WebGL = $.CubismShader_WebGL; + export const CubismShaderManager_WebGL = $.CubismShaderManager_WebGL; + export type CubismShaderManager_WebGL = $.CubismShaderManager_WebGL; + export const ShaderNames = $.ShaderNames; + export type ShaderNames = $.ShaderNames; +} diff --git a/avatar-h5-renderer/framework/src/type/csmrectf.ts b/avatar-h5-renderer/framework/src/type/csmrectf.ts new file mode 100644 index 0000000..074d727 --- /dev/null +++ b/avatar-h5-renderer/framework/src/type/csmrectf.ts @@ -0,0 +1,89 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * 矩形形状(座標・長さはfloat値)を定義するクラス + */ +export class csmRect { + /** + * コンストラクタ + * @param x 左端X座標 + * @param y 上端Y座標 + * @param w 幅 + * @param h 高さ + */ + public constructor(x?: number, y?: number, w?: number, h?: number) { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + } + + /** + * 矩形中央のX座標を取得する + */ + public getCenterX(): number { + return this.x + 0.5 * this.width; + } + + /** + * 矩形中央のY座標を取得する + */ + public getCenterY(): number { + return this.y + 0.5 * this.height; + } + + /** + * 右側のX座標を取得する + */ + public getRight(): number { + return this.x + this.width; + } + + /** + * 下端のY座標を取得する + */ + public getBottom(): number { + return this.y + this.height; + } + + /** + * 矩形に値をセットする + * @param r 矩形のインスタンス + */ + public setRect(r: csmRect): void { + this.x = r.x; + this.y = r.y; + this.width = r.width; + this.height = r.height; + } + + /** + * 矩形中央を軸にして縦横を拡縮する + * @param w 幅方向に拡縮する量 + * @param h 高さ方向に拡縮する量 + */ + public expand(w: number, h: number) { + this.x -= w; + this.y -= h; + this.width += w * 2.0; + this.height += h * 2.0; + } + + public x: number; // 左端X座標 + public y: number; // 上端Y座標 + public width: number; // 幅 + public height: number; // 高さ +} + +// Namespace definition for compatibility. +import * as $ from './csmrectf'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const csmRect = $.csmRect; + export type csmRect = $.csmRect; +} diff --git a/avatar-h5-renderer/framework/src/utils/cubismarrayutils.ts b/avatar-h5-renderer/framework/src/utils/cubismarrayutils.ts new file mode 100644 index 0000000..2f511a5 --- /dev/null +++ b/avatar-h5-renderer/framework/src/utils/cubismarrayutils.ts @@ -0,0 +1,42 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * Arrayのサイズを変更する。 + * @param curArray + * @param newSize + * @param value + * @param callPlacementNew + */ +export function updateSize( + curArray: Array, + newSize: number, + value: any = null, + callPlacementNew: boolean = null +): void { + const curSize: number = curArray.length; + + if (curSize < newSize) { + if (callPlacementNew) { + for (let i: number = curArray.length; i < newSize; i++) { + if (typeof value == 'function') { + // new + curArray[i] = JSON.parse(JSON.stringify(new value())); + } // プリミティブ型なので値渡し + else { + curArray[i] = value; + } + } + } else { + for (let i: number = curArray.length; i < newSize; i++) { + curArray[i] = value; + } + } + } else { + curArray.length = newSize; + } +} diff --git a/avatar-h5-renderer/framework/src/utils/cubismdebug.ts b/avatar-h5-renderer/framework/src/utils/cubismdebug.ts new file mode 100644 index 0000000..4d6f143 --- /dev/null +++ b/avatar-h5-renderer/framework/src/utils/cubismdebug.ts @@ -0,0 +1,162 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { + CSM_LOG_LEVEL, + CSM_LOG_LEVEL_DEBUG, + CSM_LOG_LEVEL_ERROR, + CSM_LOG_LEVEL_INFO, + CSM_LOG_LEVEL_VERBOSE, + CSM_LOG_LEVEL_WARNING +} from '../cubismframeworkconfig'; +import { CubismFramework, LogLevel } from '../live2dcubismframework'; + +export const CubismLogPrint = (level: LogLevel, fmt: string, args: any[]) => { + CubismDebug.print(level, '[CSM]' + fmt, args); +}; + +export const CubismLogPrintIn = (level: LogLevel, fmt: string, args: any[]) => { + CubismLogPrint(level, fmt + '\n', args); +}; + +export const CSM_ASSERT = (expr: any) => { + console.assert(expr); +}; + +export let CubismLogVerbose: (fmt: string, ...args: any[]) => void; +export let CubismLogDebug: (fmt: string, ...args: any[]) => void; +export let CubismLogInfo: (fmt: string, ...args: any[]) => void; +export let CubismLogWarning: (fmt: string, ...args: any[]) => void; +export let CubismLogError: (fmt: string, ...args: any[]) => void; + +if (CSM_LOG_LEVEL <= CSM_LOG_LEVEL_VERBOSE) { + CubismLogVerbose = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Verbose, '[V]' + fmt, args); + }; + + CubismLogDebug = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Debug, '[D]' + fmt, args); + }; + + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_DEBUG) { + CubismLogDebug = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Debug, '[D]' + fmt, args); + }; + + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_INFO) { + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_WARNING) { + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_ERROR) { + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} + +/** + * デバッグ用のユーティリティクラス。 + * ログの出力、バイトのダンプなど + */ +export class CubismDebug { + /** + * ログを出力する。第一引数にログレベルを設定する。 + * CubismFramework.initialize()時にオプションで設定されたログ出力レベルを下回る場合はログに出さない。 + * + * @param logLevel ログレベルの設定 + * @param format 書式付き文字列 + * @param args 可変長引数 + */ + public static print(logLevel: LogLevel, format: string, args?: any[]): void { + // オプションで設定されたログ出力レベルを下回る場合はログに出さない + if (logLevel < CubismFramework.getLoggingLevel()) { + return; + } + + const logPrint: Live2DCubismCore.csmLogFunction = + CubismFramework.coreLogFunction; + + if (!logPrint) return; + + const buffer: string = format.replace(/\{(\d+)\}/g, (m, k) => { + return args[k]; + }); + logPrint(buffer); + } + + /** + * データから指定した長さだけダンプ出力する。 + * CubismFramework.initialize()時にオプションで設定されたログ出力レベルを下回る場合はログに出さない。 + * + * @param logLevel ログレベルの設定 + * @param data ダンプするデータ + * @param length ダンプする長さ + */ + public static dumpBytes( + logLevel: LogLevel, + data: Uint8Array, + length: number + ): void { + for (let i = 0; i < length; i++) { + if (i % 16 == 0 && i > 0) this.print(logLevel, '\n'); + else if (i % 8 == 0 && i > 0) this.print(logLevel, ' '); + this.print(logLevel, '{0} ', [data[i] & 0xff]); + } + + this.print(logLevel, '\n'); + } + + /** + * private コンストラクタ + */ + private constructor() {} +} + +// Namespace definition for compatibility. +import * as $ from './cubismdebug'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismDebug = $.CubismDebug; + export type CubismDebug = $.CubismDebug; +} diff --git a/avatar-h5-renderer/framework/src/utils/cubismjson.ts b/avatar-h5-renderer/framework/src/utils/cubismjson.ts new file mode 100644 index 0000000..e87c5e9 --- /dev/null +++ b/avatar-h5-renderer/framework/src/utils/cubismjson.ts @@ -0,0 +1,1244 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { strtod } from '../live2dcubismframework'; +import { CubismLogInfo } from './cubismdebug'; + +// StaticInitializeNotForClientCall()で初期化する +const CSM_JSON_ERROR_TYPE_MISMATCH = 'Error: type mismatch'; +const CSM_JSON_ERROR_INDEX_OF_BOUNDS = 'Error: index out of bounds'; + +/** + * パースしたJSONエレメントの要素の基底クラス。 + */ +export abstract class Value { + /** + * コンストラクタ + */ + public constructor() {} + + /** + * 要素を文字列型で返す(string型) + */ + public abstract getString(defaultValue?: string, indent?: string): string; + + /** + * 要素を文字列型で返す(string) + */ + public getRawString(defaultValue?: string, indent?: string): string { + return this.getString(defaultValue, indent); + } + + /** + * 要素を数値型で返す(number) + */ + public toInt(defaultValue = 0): number { + return defaultValue; + } + + /** + * 要素を数値型で返す(number) + */ + public toFloat(defaultValue = 0): number { + return defaultValue; + } + + /** + * 要素を真偽値で返す(boolean) + */ + public toBoolean(defaultValue = false): boolean { + return defaultValue; + } + + /** + * サイズを返す + */ + public getSize(): number { + return 0; + } + + /** + * 要素を配列で返す(Value[]) + */ + public getArray(defaultValue: Value[] = null): Value[] { + return defaultValue; + } + + /** + * 要素をコンテナで返す(array) + */ + public getVector(defaultValue = new Array()): Array { + return defaultValue; + } + + /** + * 要素をマップで返す(Map) + */ + public getMap(defaultValue?: Map): Map { + return defaultValue; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 添字演算子[string] + */ + public getValueByString(s: string): Value { + return Value.nullValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * マップのキー一覧をコンテナで返す + * + * @return マップのキーの一覧 + */ + public getKeys(): Array { + return Value.dummyKeys; + } + + /** + * Valueの種類がエラー値ならtrue + */ + public isError(): boolean { + return false; + } + + /** + * Valueの種類がnullならtrue + */ + public isNull(): boolean { + return false; + } + + /** + * Valueの種類が真偽値ならtrue + */ + public isBool(): boolean { + return false; + } + + /** + * Valueの種類が数値型ならtrue + */ + public isFloat(): boolean { + return false; + } + + /** + * Valueの種類が文字列ならtrue + */ + public isString(): boolean { + return false; + } + + /** + * Valueの種類が配列ならtrue + */ + public isArray(): boolean { + return false; + } + + /** + * Valueの種類がマップ型ならtrue + */ + public isMap(): boolean { + return false; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: string): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + return false; + } + + /** + * Valueの値が静的ならtrue、静的なら解放しない + */ + public isStatic(): boolean { + return false; + } + + /** + * Valueにエラー値をセットする + */ + public setErrorNotForClientCall(errorStr: string): Value { + return JsonError.errorValue; + } + + /** + * 初期化用メソッド + */ + public static staticInitializeNotForClientCall(): void { + JsonBoolean.trueValue = new JsonBoolean(true); + JsonBoolean.falseValue = new JsonBoolean(false); + Value.errorValue = new JsonError('ERROR', true); + Value.nullValue = new JsonNullvalue(); + Value.dummyKeys = new Array(); + } + + /** + * リリース用メソッド + */ + public static staticReleaseNotForClientCall(): void { + JsonBoolean.trueValue = null; + JsonBoolean.falseValue = null; + Value.errorValue = null; + Value.nullValue = null; + Value.dummyKeys = null; + } + + protected _stringBuffer: string; // 文字列バッファ + + private static dummyKeys: Array; // ダミーキー + + public static errorValue: Value; // 一時的な返り値として返すエラー。 CubismFramework::Disposeするまではdeleteしない + public static nullValue: Value; // 一時的な返り値として返すNULL。 CubismFramework::Disposeするまではdeleteしない + + [key: string]: any; // 明示的に連想配列をany型で指定 +} + +/** + * Ascii文字のみ対応した最小限の軽量JSONパーサ。 + * 仕様はJSONのサブセットとなる。 + * 設定ファイル(model3.json)などのロード用 + * + * [未対応項目] + * ・日本語などの非ASCII文字 + * ・eによる指数表現 + */ +export class CubismJson { + /** + * コンストラクタ + */ + public constructor(buffer?: ArrayBuffer, length?: number) { + this._error = null; + this._lineCount = 0; + this._root = null; + + if (buffer != undefined) { + this.parseBytes(buffer, length, this._parseCallback); + } + } + + /** + * バイトデータから直接ロードしてパースする + * + * @param buffer バッファ + * @param size バッファサイズ + * @return CubismJsonクラスのインスタンス。失敗したらNULL + */ + public static create(buffer: ArrayBuffer, size: number) { + const json = new CubismJson(); + const succeeded: boolean = json.parseBytes( + buffer, + size, + json._parseCallback + ); + + if (!succeeded) { + CubismJson.delete(json); + return null; + } else { + return json; + } + } + + /** + * パースしたJSONオブジェクトの解放処理 + * + * @param instance CubismJsonクラスのインスタンス + */ + public static delete(instance: CubismJson) { + instance = null; + } + + /** + * パースしたJSONのルート要素を返す + */ + public getRoot(): Value { + return this._root; + } + + /** + * UnicodeのバイナリをStringに変換 + * + * @param buffer 変換するバイナリデータ + * @return 変換後の文字列 + */ + public static arrayBufferToString(buffer: ArrayBuffer): string { + const uint8Array: Uint8Array = new Uint8Array(buffer); + let str = ''; + + for (let i = 0, len: number = uint8Array.length; i < len; ++i) { + str += '%' + this.pad(uint8Array[i].toString(16)); + } + + str = decodeURIComponent(str); + return str; + } + + /** + * エンコード、パディング + */ + private static pad(n: string): string { + return n.length < 2 ? '0' + n : n; + } + + /** + * JSONのパースを実行する + * @param buffer パース対象のデータバイト + * @param size データバイトのサイズ + * return true : 成功 + * return false: 失敗 + */ + public parseBytes( + buffer: ArrayBuffer, + size: number, + parseCallback?: parseJsonObject + ): boolean { + const endPos: number[] = new Array(1); // 参照渡しにするため配列 + const decodeBuffer: string = CubismJson.arrayBufferToString(buffer); + + if (parseCallback == undefined) { + this._root = this.parseValue(decodeBuffer, size, 0, endPos); + } else { + // TypeScript標準のJSONパーサを使う + this._root = parseCallback(JSON.parse(decodeBuffer), new JsonMap()); + } + + if (this._error) { + let strbuf = '\0'; + strbuf = 'Json parse error : @line ' + (this._lineCount + 1) + '\n'; + this._root = new JsonString(strbuf); + + CubismLogInfo('{0}', this._root.getRawString()); + return false; + } else if (this._root == null) { + this._root = new JsonError(this._error, false); // rootは解放されるのでエラーオブジェクトを別途作成する + return false; + } + return true; + } + + /** + * パース時のエラー値を返す + */ + public getParseError(): string { + return this._error; + } + + /** + * ルート要素の次の要素がファイルの終端だったらtrueを返す + */ + public checkEndOfFile(): boolean { + return this._root.getArray()[1].equals('EOF'); + } + + /** + * JSONエレメントからValue(float,String,Value*,Array,null,true,false)をパースする + * エレメントの書式に応じて内部でParseString(), ParseObject(), ParseArray()を呼ぶ + * + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseValue( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ) { + if (this._error) return null; + + let o: Value = null; + let i: number = begin; + let f: number; + + for (; i < length; i++) { + const c: string = buffer[i]; + switch (c) { + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + const afterString: string[] = new Array(1); // 参照渡しにするため + f = strtod(buffer.slice(i), afterString); + outEndPos[0] = buffer.indexOf(afterString[0]); + return new JsonFloat(f); + } + case '"': + return new JsonString( + this.parseString(buffer, length, i + 1, outEndPos) + ); // \"の次の文字から + case '[': + o = this.parseArray(buffer, length, i + 1, outEndPos); + return o; + case '{': + o = this.parseObject(buffer, length, i + 1, outEndPos); + return o; + case 'n': // null以外にない + if (i + 3 < length) { + o = new JsonNullvalue(); // 解放できるようにする + outEndPos[0] = i + 4; + } else { + this._error = 'parse null'; + } + return o; + case 't': // true以外にない + if (i + 3 < length) { + o = JsonBoolean.trueValue; + outEndPos[0] = i + 4; + } else { + this._error = 'parse true'; + } + return o; + case 'f': // false以外にない + if (i + 4 < length) { + o = JsonBoolean.falseValue; + outEndPos[0] = i + 5; + } else { + this._error = "illegal ',' position"; + } + return o; + case ',': // Array separator + this._error = "illegal ',' position"; + return null; + case ']': // 不正な}だがスキップする。配列の最後に不要な , があると思われる + outEndPos[0] = i; // 同じ文字を再処理 + return null; + case '\n': + this._lineCount++; + // falls through + case ' ': + case '\t': + case '\r': + default: + // スキップ + break; + } + } + + this._error = 'illegal end of value'; + return null; + } + + /** + * 次の「"」までの文字列をパースする。 + * + * @param string -> パース対象の文字列 + * @param length -> パースする長さ + * @param begin -> パースを開始する位置 + * @param outEndPos -> パース終了時の位置 + * @return パースした文F字列要素 + */ + protected parseString( + string: string, + length: number, + begin: number, + outEndPos: number[] + ): string { + if (this._error) { + return null; + } + + if (!string) { + this._error = 'string is null'; + return null; + } + + let i = begin; + let c: string, c2: string; + let ret: string = ''; + let bufStart: number = begin; // sbufに登録されていない文字の開始位置 + + for (; i < length; i++) { + c = string[i]; + + switch (c) { + case '"': { + // 終端の”、エスケープ文字は別に処理されるのでここに来ない + outEndPos[0] = i + 1; // ”の次の文字 + ret += string.substr(bufStart, i - bufStart); // 前の文字までを登録する + return ret; + } + // falls through + case '//': { + // エスケープの場合 + i++; // 2文字をセットで扱う + + if (i - 1 > bufStart) { + ret += string.substr(bufStart, i - bufStart); // 前の文字までを登録する + } + bufStart = i + 1; // エスケープ(2文字)の次の文字から + + if (i < length) { + c2 = string[i]; + + switch (c2) { + case '\\': + ret += '\\'; + break; + case '"': + ret += '"'; + break; + case '/': + ret += '/'; + break; + case 'b': + ret += '\b'; + break; + case 'f': + ret += '\f'; + break; + case 'n': + ret += '\n'; + break; + case 'r': + ret += '\r'; + break; + case 't': + ret += '\t'; + break; + case 'u': + this._error = 'parse string/unicord escape not supported'; + break; + default: + break; + } + } else { + this._error = 'parse string/escape error'; + } + } + // falls through + default: { + break; + } + } + } + + this._error = 'parse string/illegal end'; + return null; + } + + /** + * JSONのオブジェクトエレメントをパースしてValueオブジェクトを返す + * + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseObject( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ): Value { + if (this._error) { + return null; + } + + if (!buffer) { + this._error = 'buffer is null'; + return null; + } + + const ret: JsonMap = new JsonMap(); + + // Key: Value + let key = ''; + let i: number = begin; + let c = ''; + const localRetEndPos2: number[] = Array(1); + let ok = false; + + // , が続く限りループ + for (; i < length; i++) { + FOR_LOOP: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case '"': + key = this.parseString(buffer, length, i + 1, localRetEndPos2); + if (this._error) { + return null; + } + + i = localRetEndPos2[0]; + ok = true; + break FOR_LOOP; //-- loopから出る + case '}': // 閉じカッコ + outEndPos[0] = i + 1; + return ret; // 空 + case ':': + this._error = "illegal ':' position"; + break; + case '\n': + this._lineCount++; + // falls through + default: + break; // スキップする文字 + } + } + if (!ok) { + this._error = 'key not found'; + return null; + } + + ok = false; + + // : をチェック + FOR_LOOP2: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ':': + ok = true; + i++; + break FOR_LOOP2; + case '}': + this._error = "illegal '}' position"; + break; + // falls through + case '\n': + this._lineCount++; + // case ' ': case '\t' : case '\r': + // falls through + default: + break; // スキップする文字 + } + } + + if (!ok) { + this._error = "':' not found"; + return null; + } + + // 値をチェック + const value: Value = this.parseValue(buffer, length, i, localRetEndPos2); + if (this._error) { + return null; + } + + i = localRetEndPos2[0]; + + // ret.put(key, value); + ret.put(key, value); + + FOR_LOOP3: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ',': + break FOR_LOOP3; + case '}': + outEndPos[0] = i + 1; + return ret; // 正常終了 + case '\n': + this._lineCount++; + // falls through + default: + break; // スキップ + } + } + } + + this._error = 'illegal end of perseObject'; + return null; + } + + /** + * 次の「"」までの文字列をパースする。 + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseArray( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ): Value { + if (this._error) { + return null; + } + + if (!buffer) { + this._error = 'buffer is null'; + return null; + } + + let ret: JsonArray = new JsonArray(); + + // key : value + let i: number = begin; + let c: string; + const localRetEndpos2: number[] = new Array(1); + + // , が続く限りループ + for (; i < length; i++) { + // : をチェック + const value: Value = this.parseValue(buffer, length, i, localRetEndpos2); + + if (this._error) { + return null; + } + i = localRetEndpos2[0]; + + if (value) { + ret.add(value); + } + + // FOR_LOOP3: + // boolean breakflag = false; + FOR_LOOP: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ',': + // breakflag = true; + // break; // 次のKEY, VAlUEへ + break FOR_LOOP; + case ']': + outEndPos[0] = i + 1; + return ret; // 終了 + case '\n': + ++this._lineCount; + //case ' ': case '\t': case '\r': + // falls through + default: + break; // スキップ + } + } + } + + ret = void 0; + this._error = 'illegal end of parseObject'; + return null; + } + + _parseCallback: parseJsonObject = CubismJsonExtension.parseJsonObject; // パース時に使う処理のコールバック関数 + + _error: string; // パース時のエラー + _lineCount: number; // エラー報告に用いる行数カウント + _root: Value; // パースされたルート要素 +} + +interface parseJsonObject { + (obj: Value, map: JsonMap): JsonMap; +} + +/** + * パースしたJSONの要素をfloat値として扱う + */ +export class JsonFloat extends Value { + /** + * コンストラクタ + */ + constructor(v: number) { + super(); + + this._value = v; + } + + /** + * Valueの種類が数値型ならtrue + */ + public isFloat(): boolean { + return true; + } + + /** + * 要素を文字列で返す(string型) + */ + public getString(defaultValue: string, indent: string): string { + const strbuf = '\0'; + this._value = parseFloat(strbuf); + this._stringBuffer = strbuf; + + return this._stringBuffer; + } + + /** + * 要素を数値型で返す(number) + */ + public toInt(defaultValue = 0): number { + return parseInt(this._value.toString()); + } + + /** + * 要素を数値型で返す(number) + */ + public toFloat(defaultValue = 0.0): number { + return this._value; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: string): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('number' === typeof value) { + // int + if (Math.round(value)) { + return false; + } + // float + else { + return value == this._value; + } + } + return false; + } + + private _value: number; // JSON要素の値 +} + +/** + * パースしたJSONの要素を真偽値として扱う + */ +export class JsonBoolean extends Value { + /** + * Valueの種類が真偽値ならtrue + */ + public isBool(): boolean { + return true; + } + + /** + * 要素を真偽値で返す(boolean) + */ + public toBoolean(defaultValue = false): boolean { + return this._boolValue; + } + + /** + * 要素を文字列で返す(string型) + */ + public getString(defaultValue: string, indent: string): string { + this._stringBuffer = this._boolValue ? 'true' : 'false'; + + return this._stringBuffer; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: string): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('boolean' === typeof value) { + return value == this._boolValue; + } + return false; + } + + /** + * Valueの値が静的ならtrue, 静的なら解放しない + */ + public isStatic(): boolean { + return true; + } + + /** + * 引数付きコンストラクタ + */ + public constructor(v: boolean) { + super(); + + this._boolValue = v; + } + + static trueValue: JsonBoolean; // true + static falseValue: JsonBoolean; // false + + private _boolValue: boolean; // JSON要素の値 +} + +/** + * パースしたJSONの要素を文字列として扱う + */ +export class JsonString extends Value { + /** + * 引数付きコンストラクタ + */ + public constructor(s: string) { + super(); + this._stringBuffer = s; + } + + /** + * Valueの種類が文字列ならtrue + */ + public isString(): boolean { + return true; + } + + /** + * 要素を文字列で返す(string型) + */ + public getString(defaultValue: string, indent: string): string { + return this._stringBuffer; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: string): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('string' === typeof value) { + return this._stringBuffer == value; + } + + return false; + } +} + +/** + * JSONパース時のエラー結果。文字列型のようにふるまう + */ +export class JsonError extends JsonString { + /** + * Valueの値が静的ならtrue、静的なら解放しない + */ + public isStatic(): boolean { + return this._isStatic; + } + + /** + * エラー情報をセットする + */ + public setErrorNotForClientCall(s: string): Value { + this._stringBuffer = s; + return this; + } + + /** + * 引数付きコンストラクタ + */ + public constructor(s: string, isStatic: boolean) { + if ('string' === typeof s) { + super(s); + } else { + super(s); + } + this._isStatic = isStatic; + } + + /** + * Valueの種類がエラー値ならtrue + */ + public isError(): boolean { + return true; + } + + protected _isStatic: boolean; // 静的なValueかどうか +} + +/** + * パースしたJSONの要素をNULL値として持つ + */ +export class JsonNullvalue extends Value { + /** + * Valueの種類がNULL値ならtrue + */ + public isNull(): boolean { + return true; + } + + /** + * 要素を文字列で返す(string型) + */ + public getString(defaultValue: string, indent: string): string { + return this._stringBuffer; + } + + /** + * Valueの値が静的ならtrue, 静的なら解放しない + */ + public isStatic(): boolean { + return true; + } + + /** + * Valueにエラー値をセットする + */ + public setErrorNotForClientCall(s: string): Value { + this._stringBuffer = s; + return JsonError.nullValue; + } + + /** + * コンストラクタ + */ + public constructor() { + super(); + + this._stringBuffer = 'NullValue'; + } +} + +/** + * パースしたJSONの要素を配列として持つ + */ +export class JsonArray extends Value { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._array = new Array(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for (let i = 0; i < this._array.length; i++) { + let v: Value = this._array[i]; + if (v && !v.isStatic()) { + v = void 0; + v = null; + } + } + } + + /** + * Valueの種類が配列ならtrue + */ + public isArray(): boolean { + return true; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + if (index < 0 || this._array.length <= index) { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_INDEX_OF_BOUNDS + ); + } + + const v: Value = this._array[index]; + + if (v == null) { + return Value.nullValue; + } + + return v; + } + + /** + * 添字演算子[string] + */ + public getValueByString(s: string): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 要素を文字列で返す(string型) + */ + public getString(defaultValue: string, indent: string): string { + const stringBuffer: string = indent + '[\n'; + + for (let i = 0; i < this._array.length; i++) { + const v: Value = this._array[i]; + this._stringBuffer += indent + '' + v.getString(indent + ' ') + '\n'; + } + + this._stringBuffer = stringBuffer + indent + ']\n'; + + return this._stringBuffer; + } + + /** + * 配列要素を追加する + * @param v 追加する要素 + */ + public add(v: Value): void { + this._array.push(v); + } + + /** + * 要素をコンテナで返す(Array) + */ + public getVector(defaultValue: Array = null): Array { + return this._array; + } + + /** + * 要素の数を返す + */ + public getSize(): number { + return this._array.length; + } + + private _array: Array; // JSON要素の値 +} + +/** + * パースしたJSONの要素をマップとして持つ + */ +export class JsonMap extends Value { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._map = new Map(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._map.clear(); + } + + /** + * Valueの値がMap型ならtrue + */ + public isMap(): boolean { + return true; + } + + /** + * 添字演算子[string] + */ + public getValueByString(s: string): Value { + const ret = this._map.get(s); + if (ret != undefined) { + return ret; + } + return Value.nullValue; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 要素を文字列で返す(string型) + */ + public getString(defaultValue: string, indent: string) { + this._stringBuffer = indent + '{\n'; + + for (const element of this._map) { + const key = element[0]; + const v: Value = element[1]; + + this._stringBuffer += + indent + ' ' + key + ' : ' + v.getString(indent + ' ') + ' \n'; + } + + this._stringBuffer += indent + '}\n'; + + return this._stringBuffer; + } + + /** + * 要素をMap型で返す + */ + public getMap(defaultValue?: Map): Map { + return this._map; + } + + /** + * Mapに要素を追加する + */ + public put(key: string, v: Value): void { + this._map.set(key, v); + } + + /** + * Mapからキーのリストを取得する + */ + public getKeys(): Array { + if (!this._keys) { + this._keys = [...this._map.keys()]; + } + return this._keys; + } + + /** + * Mapの要素数を取得する + */ + public getSize(): number { + return this._keys.length; + } + + private _map: Map; // JSON要素の値 + private _keys: Array; // JSON要素の値 +} + +// Namespace definition for compatibility. +import * as $ from './cubismjson'; +import { CubismJsonExtension } from './cubismjsonextension'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismJson = $.CubismJson; + export type CubismJson = $.CubismJson; + export const JsonArray = $.JsonArray; + export type JsonArray = $.JsonArray; + export const JsonBoolean = $.JsonBoolean; + export type JsonBoolean = $.JsonBoolean; + export const JsonError = $.JsonError; + export type JsonError = $.JsonError; + export const JsonFloat = $.JsonFloat; + export type JsonFloat = $.JsonFloat; + export const JsonMap = $.JsonMap; + export type JsonMap = $.JsonMap; + export const JsonNullvalue = $.JsonNullvalue; + export type JsonNullvalue = $.JsonNullvalue; + export const JsonString = $.JsonString; + export type JsonString = $.JsonString; + export const Value = $.Value; + export type Value = $.Value; +} diff --git a/avatar-h5-renderer/framework/src/utils/cubismjsonextension.ts b/avatar-h5-renderer/framework/src/utils/cubismjsonextension.ts new file mode 100644 index 0000000..6a0a1c6 --- /dev/null +++ b/avatar-h5-renderer/framework/src/utils/cubismjsonextension.ts @@ -0,0 +1,99 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { + JsonArray, + JsonBoolean, + JsonFloat, + JsonMap, + JsonNullvalue, + JsonString, + Value +} from './cubismjson'; + +/** + * CubismJsonで実装されているJsonパーサを使用せず、 + * TypeScript標準のJsonパーサなどを使用し出力された結果を + * Cubism SDKで定義されているJSONエレメントの要素に + * 置き換える処理をするクラス。 + */ +export class CubismJsonExtension { + static parseJsonObject(obj: Value, map: JsonMap) { + Object.keys(obj).forEach(key => { + if (typeof obj[key] == 'boolean') { + const convValue = Boolean(obj[key]); + map.put(key, new JsonBoolean(convValue)); + } else if (typeof obj[key] == 'string') { + const convValue = String(obj[key]); + map.put(key, new JsonString(convValue)); + } else if (typeof obj[key] == 'number') { + const convValue = Number(obj[key]); + map.put(key, new JsonFloat(convValue)); + } else if (obj[key] instanceof Array) { + // HACK: Array 単体で変換できないので unknown に変更してから Value にしている + map.put( + key, + CubismJsonExtension.parseJsonArray(obj[key] as unknown as Value) + ); + } else if (obj[key] instanceof Object) { + map.put( + key, + CubismJsonExtension.parseJsonObject(obj[key], new JsonMap()) + ); + } else if (obj[key] == null) { + map.put(key, new JsonNullvalue()); + } else { + // どれにも当てはまらない場合でも処理する + map.put(key, obj[key]); + } + }); + return map; + } + + protected static parseJsonArray(obj: Value) { + const arr = new JsonArray(); + Object.keys(obj).forEach(key => { + const convKey = Number(key); + if (typeof convKey == 'number') { + if (typeof obj[key] == 'boolean') { + const convValue = Boolean(obj[key]); + arr.add(new JsonBoolean(convValue)); + } else if (typeof obj[key] == 'string') { + const convValue = String(obj[key]); + arr.add(new JsonString(convValue)); + } else if (typeof obj[key] == 'number') { + const convValue = Number(obj[key]); + arr.add(new JsonFloat(convValue)); + } else if (obj[key] instanceof Array) { + // HACK: Array 単体で変換できないので unknown に変更してから Value にしている + arr.add(this.parseJsonArray(obj[key] as unknown as Value)); + } else if (obj[key] instanceof Object) { + arr.add(this.parseJsonObject(obj[key], new JsonMap())); + } else if (obj[key] == null) { + arr.add(new JsonNullvalue()); + } else { + // どれにも当てはまらない場合でも処理する + arr.add(obj[key]); + } + } else if (obj[key] instanceof Array) { + // HACK: Array 単体で変換できないので unknown に変更してから Value にしている + arr.add(this.parseJsonArray(obj[key] as unknown as Value)); + } else if (obj[key] instanceof Object) { + arr.add(this.parseJsonObject(obj[key], new JsonMap())); + } else if (obj[key] == null) { + arr.add(new JsonNullvalue()); + } else { + const convValue = Array(obj[key]); + // 配列ともObjectとも判定できなかった場合でも処理する + for (let i = 0; i < convValue.length; i++) { + arr.add(convValue[i]); + } + } + }); + return arr; + } +} diff --git a/avatar-h5-renderer/framework/src/utils/cubismstring.ts b/avatar-h5-renderer/framework/src/utils/cubismstring.ts new file mode 100644 index 0000000..eaeccd4 --- /dev/null +++ b/avatar-h5-renderer/framework/src/utils/cubismstring.ts @@ -0,0 +1,129 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export class CubismString { + /** + * 標準出力の書式を適用した文字列を取得する。 + * @param format 標準出力の書式指定文字列 + * @param ...args 書式指定文字列に渡す文字列 + * @return 書式を適用した文字列 + */ + public static getFormatedString(format: string, ...args: any[]): string { + const ret: string = format; + return ret.replace( + /\{(\d+)\}/g, + ( + m, + k // m="{0}", k="0" + ) => { + return args[k]; + } + ); + } + + /** + * textがstartWordで始まっているかどうかを返す + * @param test 検査対象の文字列 + * @param startWord 比較対象の文字列 + * @return true textがstartWordで始まっている + * @return false textがstartWordで始まっていない + */ + public static isStartWith(text: string, startWord: string): boolean { + let textIndex = 0; + let startWordIndex = 0; + while (startWord[startWordIndex] != '\0') { + if ( + text[textIndex] == '\0' || + text[textIndex++] != startWord[startWordIndex++] + ) { + return false; + } + } + return false; + } + + /** + * position位置の文字から数字を解析する。 + * + * @param string 文字列 + * @param length 文字列の長さ + * @param position 解析したい文字の位置 + * @param outEndPos 一文字も読み込まなかった場合はエラー値(-1)が入る + * @return 解析結果の数値 + */ + public static stringToFloat( + string: string, + length: number, + position: number, + outEndPos: number[] + ): number { + let i: number = position; + let minus = false; // マイナスフラグ + let period = false; + let v1 = 0; + + //負号の確認 + let c: number = parseInt(string[i]); + if (c < 0) { + minus = true; + i++; + } + + //整数部の確認 + for (; i < length; i++) { + const c = string[i]; + if (0 <= parseInt(c) && parseInt(c) <= 9) { + v1 = v1 * 10 + (parseInt(c) - 0); + } else if (c == '.') { + period = true; + i++; + break; + } else { + break; + } + } + + //小数部の確認 + if (period) { + let mul = 0.1; + for (; i < length; i++) { + c = parseFloat(string[i]) & 0xff; + if (0 <= c && c <= 9) { + v1 += mul * (c - 0); + } else { + break; + } + mul *= 0.1; //一桁下げる + if (!c) break; + } + } + + if (i == position) { + //一文字も読み込まなかった場合 + outEndPos[0] = -1; //エラー値が入るので呼び出し元で適切な処理を行う + return 0; + } + + if (minus) v1 = -v1; + + outEndPos[0] = i; + return v1; + } + + /** + * コンストラクタ呼び出し不可な静的クラスにする。 + */ + private constructor() {} +} + +// Namespace definition for compatibility. +import * as $ from './cubismstring'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismString = $.CubismString; + export type CubismString = $.CubismString; +} diff --git a/avatar-h5-renderer/framework/tsconfig.json b/avatar-h5-renderer/framework/tsconfig.json new file mode 100644 index 0000000..fb9ff46 --- /dev/null +++ b/avatar-h5-renderer/framework/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es6", + "moduleResolution": "node", + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "emitDecoratorMetadata": true, + "noImplicitAny": true, + "useUnknownInCatchVariables": true + }, + "include": [ + "src/**/*.ts", + "../Core/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/avatar-h5-renderer/index.html b/avatar-h5-renderer/index.html new file mode 100644 index 0000000..2e5154f --- /dev/null +++ b/avatar-h5-renderer/index.html @@ -0,0 +1,269 @@ + + + + + + Avatar H5 Renderer (PoC) + + + + + + + +
+
+

角色状态

+
+ + + + +
+
+ +
+

表情切换

+
+ 加载中... +
+
+ +
+

动作播放

+
+ 加载中... +
+
+ +
+

嘴型驱动 0.00

+ +
+ + + +
+
+ +
+

语义动作

+
+
对应未来 LLM Function Call 触发
+
+ +
+

跳舞

+
+ + +
+
程序化驱动身体参数 + 循环切 Dance motion
+
+ +
+

事件模拟

+
+ +
+
listening → thinking → 微笑+说话 → idle
+
+ +
+

Console API

+
+ 浏览器控制台可直接调:
+ avatar.setExpression("Smile")
+ avatar.playMotion("Idle", 0)
+ avatar.setMouthOpen(0.8)
+ avatar.listExpressions()
+ avatar.listMotions() +
+
+
+ + + + diff --git a/avatar-h5-renderer/package-lock.json b/avatar-h5-renderer/package-lock.json new file mode 100644 index 0000000..aa4373e --- /dev/null +++ b/avatar-h5-renderer/package-lock.json @@ -0,0 +1,3868 @@ +{ + "name": "avatar-h5-renderer", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@eslint/js": "^9.39.2", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "prettier": "^3.8.1", + "rimraf": "^6.1.3", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.2", + "vite": "^8.0.2" + }, + "optionalDependencies": { + "fsevents": "*" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz", + "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz", + "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz", + "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", + "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/type-utils": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.57.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", + "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", + "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz", + "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.11" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-x64": "1.0.0-rc.11", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", + "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz", + "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.11", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, + "@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.4.3" + } + }, + "@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true + }, + "@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "requires": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + } + }, + "@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "requires": { + "@eslint/core": "^0.17.0" + } + }, + "@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15" + } + }, + "@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true + }, + "@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true + }, + "@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "requires": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + } + }, + "@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true + }, + "@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "requires": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "dependencies": { + "@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true + } + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true + }, + "@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true + }, + "@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true + }, + "@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==", + "dev": true, + "optional": true + }, + "@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz", + "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==", + "dev": true, + "optional": true + }, + "@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz", + "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==", + "dev": true, + "optional": true, + "requires": { + "@napi-rs/wasm-runtime": "^1.1.1" + } + }, + "@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==", + "dev": true, + "optional": true + }, + "@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==", + "dev": true, + "optional": true + }, + "@rolldown/pluginutils": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz", + "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==", + "dev": true + }, + "@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", + "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/type-utils": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "dependencies": { + "ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true + } + } + }, + "@typescript-eslint/parser": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", + "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3" + } + }, + "@typescript-eslint/project-service": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "dev": true, + "requires": { + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", + "debug": "^4.4.3" + } + }, + "@typescript-eslint/scope-manager": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" + } + }, + "@typescript-eslint/tsconfig-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "dev": true, + "requires": {} + }, + "@typescript-eslint/type-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", + "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + } + }, + "@typescript-eslint/types": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "dev": true, + "requires": { + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "dependencies": { + "minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.57.2", + "eslint-visitor-keys": "^5.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true + } + } + }, + "acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + }, + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + } + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "requires": {} + }, + "eslint-plugin-prettier": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + } + }, + "eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "requires": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true + } + } + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "requires": { + "flat-cache": "^4.0.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + } + }, + "flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "optional": true + }, + "glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "requires": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "dependencies": { + "minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + } + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, + "import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "requires": { + "detect-libc": "^2.0.3", + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "dev": true, + "optional": true + }, + "lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "dev": true, + "optional": true + }, + "lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "dev": true, + "optional": true + }, + "lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "dev": true, + "optional": true + }, + "lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "dev": true, + "optional": true + }, + "lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "dev": true, + "optional": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + } + } + }, + "minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + }, + "postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "requires": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "dev": true, + "requires": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + } + }, + "rolldown": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz", + "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==", + "dev": true, + "requires": { + "@oxc-project/types": "=0.122.0", + "@rolldown/binding-android-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-x64": "1.0.0-rc.11", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11", + "@rolldown/pluginutils": "1.0.0-rc.11" + } + }, + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "requires": { + "@pkgr/core": "^0.2.9" + } + }, + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + } + } + }, + "ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "requires": {} + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true + }, + "typescript-eslint": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", + "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", + "dev": true, + "requires": { + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "vite": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz", + "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==", + "dev": true, + "requires": { + "fsevents": "~2.3.3", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.11", + "tinyglobby": "^0.2.15" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/avatar-h5-renderer/package.json b/avatar-h5-renderer/package.json new file mode 100644 index 0000000..8f80490 --- /dev/null +++ b/avatar-h5-renderer/package.json @@ -0,0 +1,28 @@ +{ + "private": true, + "scripts": { + "start": "node copy_resources.js && vite --host", + "build": "tsc --noEmit && node copy_resources.js && vite build --mode development", + "build:prod": "node copy_resources.js && vite build", + "copy_resources": "node copy_resources.js", + "test": "tsc --noEmit", + "lint": "eslint", + "lint:fix": "eslint --fix", + "serve": "vite preview --port 5000 --host", + "clean": "rimraf dist" + }, + "devDependencies": { + "@eslint/js": "^9.39.2", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "prettier": "^3.8.1", + "rimraf": "^6.1.3", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.2", + "vite": "^8.0.2" + }, + "optionalDependencies": { + "fsevents": "*" + } +} diff --git a/avatar-h5-renderer/public/Core/CHANGELOG.md b/avatar-h5-renderer/public/Core/CHANGELOG.md new file mode 100644 index 0000000..f7c9c1c --- /dev/null +++ b/avatar-h5-renderer/public/Core/CHANGELOG.md @@ -0,0 +1,426 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + + +## 2026-04-02 + +### Removed + +* [Unity] Remove Android x86 library. + + +## 2026-01-29 + +### Added + +* Add Arm64 static library for iphonesimulator. + + +## 2026-01-08 + +### Changed + +* Upgrade Core version to 06.00.0001. + +### Fixed + +* Fix offscreen opacity calculation. + + +## 2025-10-30 + +### Removed + +* [Native] Remove Visual Studio 2015 (MSVC 140) static library. + + +## 2025-10-14 + +### Added + +* Add `csmGetMocVersion(mocBytes: ArrayBuffer)` with simplified arguments from `csmGetMocVersion(moc: Moc, mocBytes: ArrayBuffer)`. + * The previous version `csmGetMocVersion(moc: Moc, mocBytes: ArrayBuffer)` is now deprecated and is planned to be removed in the future. + + +## 2025-08-26 + +### Added + +* Enhanced model rendering features have been added. + +### Changed + +* Upgrade Core version to 06.00.0000. + + +## 2025-07-17 + +### Changed + +* [Unity,Native,Java] Implement support for Android 16KB page size. + + +## 2025-04-24 + +### Added + +* Add the function `csmGetParameterRepeats`. + * This function retrieves whether the parameters are set to repeat. + +### Changed + +* Upgrade Core version to 05.01.0000. + +### Fixed + +* Fix `csmGetParameterKeyCounts()` and `csmGetParameterKeyValues()` symbols in the DLL. + +### Changed + +* Upgrade Core version to 05.01.0000. + + +## 2024-12-19 + +### Removed + +* [Native] Remove Visual Studio 2013 (MSVC 120) static library. + + +## 2024-11-07 + +### Added + +* [Native] Add experimental support `arm64` library for linux. + +### Removed + +* [Unity,Native,Java] Remove Android ARM v7 library. + + +## 2024-04-04 + +### Added + +* [Unity] Add library(.so) for HarmonyOS build. + + +## 2024-03-26 + +### Remove + +* [Unity] Remove built with Emscripten 1.38.48. + * Unity 2021.2 or later uses only Core under `Assets/Live2D/Cubism/Plugins/Experimental/Emscripten/latest`. + + +## 2023-09-28 + +### Remove + +* Remove bitcode from IOS build. + + +## 2023-08-17 + +### Added + +* Enhance Blend Shape features. + * Please see [here](https://docs.live2d.com/en/cubism-editor-manual/blend-shape/). + +### Changed + +* Upgrade Core version to 05.00.0000. + + +## 2023-05-09 + +### Changed + +* Change the GCC version of the library for Linux from 6.5.0 to 8.3.0. + + +## 2023-03-16 + +### Fixed + +* Fix a case in which the index of the mask's drawable object was negative value for `csmGetDrawableMasks()`. +* Fix a problem in which `csmHasMocConsistency()` was returned as 0 even though the MOC3 file was in the correct format. + * This problem was occurring in some models using the blendshape weight limit settings. +* Fix a problem that could cause a crash if a MOC3 file that is not in the correct format is loaded with `csmHasMocConsistency()`. + +### Changed + +* Upgrade Core version to 04.02.0004. + + +## 2023-03-10 + +### Added + +* Add the function `csmHasMocConsistency`. + * This function verifies that the `MOC3` file is valid. + +### Changed + +* Upgrade Core version to 04.02.0003. + + +## 2023-02-21 + +### Added + +* [Web] Added classes related to `Memory`. + * Add the funciton `initializeAmountOfMemory()` to adjust the amount of memory at initialization. + + +## 2022-10-28 + +### Fixed + +* [Java] Remove unnecessary methods. + + +## 2022-10-06 + +### Added + +* [Java] Add AAR file for Android. + + +## 2022-09-08 + +### Added + +* Add the multilingual supported documents. +* Support Visual Studio 2022. + + +## 2022-08-04 + +### Fixed + +* [Web] Fix `csmGetMocVersion` function argument. + + +## 2022-07-07 + +### Added + +* Add functions + * `csmGetParameterTypes` + * `csmGetDrawableParentPartIndices` + +* Add type `csmMocVersion` and enum. This type is the return value of `csmGetMocVersion`, `csmGetLatestMocVersion`. + +### Changed + +* Upgrade Core version to 04.02.0002. + + +## 2022-06-02 + +### Changed + +* Upgrade Core version to 04.02.0001. + +### Fixed + +* Fixed a bug that caused Multiply Color / Screen Color of different objects to be applied. + + +## 2022-05-19 + +### Added + +* Support new Multiply Color / Screen Color features. +* Support new Blend Shape features. + +### Changed + +* Upgrade Core version to 04.02.0000. This upgrade is following Cubism Editor 4.2 features. + + +## 2022-02-10 + +### Added + +* [Unity] Add bitcode library(.bc) for Emscripten latest version build. + +### Changed + +* [Unity] Change the bitcode file directory location. + * emsdk latest version build bitcode file in `latest` directory. + * emsdk 1.38.48 build bitcode file in `1_38_48` directory. + + +## 2021-12-09 + +### Added + +* Add static library(.a) for Mac Catalyst. + + +## 2021-10-07 + +### Added + +* Add `x86_64` library for Android. +* Add `arm64` library for macOS. + + +## 2021-03-09 + +### Added + +* Add funtcions for Viewer. + * `csmGetParameterKeyCounts` + * `csmGetParameterKeyValues` + + +### Changed + +* Update Core version to `04.01.0000`. + + +## 2020-01-30 + +### Added + +* Add static library(.lib) for statically linking DLL. +* Add symbol file for Windows dynamic library (dll). + + +## 2019-11-19 + +### Fixed + +* Fix linking static libraries for Windows (.lib). + + +## 2019-11-14 + +### Added + +* Support Visual Studio 2019. +* Support macOS dynamic library (dylib). + +### Changed + +* Update Windows dynamic library: Use Visual Studio 2019 for building. + +### Security + +* Bundle certificate and notary ticket to macOS shared library. + + +## 2019-09-04 + +### Added + +* Support new Inverted Masking features. +* Support ARM64 architecture for Universal Windows Platform. + +### Changed + +* Upgrade Core version to 04.00.0000 (67108864). This upgrade is following Cubism Editor 4.0 features. +* Add calling convention for *Windows/x86 DLL* only. + +### Removed + +* Remove bitcode binary due to suspension of *Cubism Bindings.* + + +## 2019-04-09 + +### Added + +* Support Universal Windows Platform for Windows Store Application. + + +## 2019-01-31 + +### Added + +* Add API to get the parent part of the specified part. +* Add API to get moc3 version. + + +## 2018-12-20 + +### Added + +* [Native] Add new function: `csmGetPartParentPartIndices`. +* [Native, 3.3 Support] Support new Warp Deformer features. + +### Changed + +* Upgrade Core version to 03.03.0000 (50528256). This upgrade is following Cubism Editor 3.3 features. + + +## 2018-08-22 + +### Added + +* [Native] Add support for Neon. + + +## 2018-05-14 + +### Added + +* [Native] Add Windows **Visual C++ 2013** library. +* [Windows] Add runtime library choice `MT`, `MD`, `MTd`, `MDd`. +* [iOS] Add support for iPhone Simulator SDK. + +### Fixed + +* Fix an error occurred when linking libraries for Android `arm64-v8a`. + + +## 2017-11-17 + +### Fixed + +* Fix processing of vertex index. + + +## 2017-10-05 + +### Added + +* Provide bitcode for iOS. + + +## 2017-08-09 + +### Added + +* [Native] Add Android *arm64-v8a* ABI library. + +### Fixed + +* Fix drawing order in certain scenarios. + + +## 2017-07-12 + +### Added + +* Add experimental support for Emscripten. +* Add `CHANGELOG.md`. + +### Fixed + +* Fix access violation in certain scenarios. +* Fix update result in certain scenarios. + + +## 2017-05-02 + +### Added + +* [Native] Add experimental support for Raspberry PI. +* Add `README.md`. diff --git a/avatar-h5-renderer/public/Core/LICENSE.md b/avatar-h5-renderer/public/Core/LICENSE.md new file mode 100644 index 0000000..4139d4f --- /dev/null +++ b/avatar-h5-renderer/public/Core/LICENSE.md @@ -0,0 +1,7 @@ +## Live2D Proprietary Software License + +Live2D Cubism Core is available under Live2D Proprietary Software License. + +* [Live2D Proprietary Software License Agreement](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html) +* [Live2D Proprietary Software 使用許諾契約書](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_jp.html) +* [Live2D Proprietary Software 使用授权协议](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_cn.html) diff --git a/avatar-h5-renderer/public/Core/README.ja.md b/avatar-h5-renderer/public/Core/README.ja.md new file mode 100644 index 0000000..359c71a --- /dev/null +++ b/avatar-h5-renderer/public/Core/README.ja.md @@ -0,0 +1,30 @@ +[English](README.md) / [日本語](README.ja.md) + +--- + +# Live2D Cubism Core + +このフォルダーには、JavaScriptまたはTypeScriptアプリケーションを開発するためのコアライブラリファイルが含まれています。 + + +## ファイルリスト + +### live2dcubismcore.d.ts + +このファイルには、`live2dcubismcore.js`に関するTypeScriptの型情報が含まれています。 +TypeScriptで開発する場合は、このファイルを`live2dcubismcore.js`とともに使用してください。 + +### live2dcubismcore.js + +このファイルには、CubismCoreの機能といくつかのラッパーが含まれています。 +JavaScriptで開発する場合は、このファイルを使用してください。 + +### live2dcubismcore.js.map + +このファイルは、`live2dcubismcore.d.ts`と`live2dcubismcore.js`の間のソースマップです。 +デバッグ時にこのファイルを使用します。 + +### live2dcubismcore.min.js + +このファイルは、`live2dcubismcore.js`のminify版です。 +このファイルを本番環境で使用します。 diff --git a/avatar-h5-renderer/public/Core/README.md b/avatar-h5-renderer/public/Core/README.md new file mode 100644 index 0000000..c8764e9 --- /dev/null +++ b/avatar-h5-renderer/public/Core/README.md @@ -0,0 +1,30 @@ +[English](README.md) / [日本語](README.ja.md) + +--- + +# Live2D Cubism Core + +This folder contains core library files for developing JavaScript or TypeScript applications. + + +## File List + +### live2dcubismcore.d.ts + +This file contains typescript type information about `live2dcubismcore.js`. +Use this file with `live2dcubismcore.js` when developing with TypeScript. + +### live2dcubismcore.js + +This file contains Cubism Core features and some wrapper features. +Use this file when developing with JavaScript. + +### live2dcubismcore.js.map + +This file is the source map between `live2dcubismcore.d.ts` and `live2dcubismcore.js`. +Use this file when debugging. + +### live2dcubismcore.min.js + +This file is the minified version of `live2dcubismcore.js`. +Use this file in production. diff --git a/avatar-h5-renderer/public/Core/RedistributableFiles.txt b/avatar-h5-renderer/public/Core/RedistributableFiles.txt new file mode 100644 index 0000000..daf7a73 --- /dev/null +++ b/avatar-h5-renderer/public/Core/RedistributableFiles.txt @@ -0,0 +1,6 @@ +The following is a list of files available for redistribution +under the terms of the Live2D Proprietary Software License Agreement: + +- live2dcubismcore.d.ts +- live2dcubismcore.js +- live2dcubismcore.min.js diff --git a/avatar-h5-renderer/public/Core/live2dcubismcore.d.ts b/avatar-h5-renderer/public/Core/live2dcubismcore.d.ts new file mode 100644 index 0000000..b2c7f81 --- /dev/null +++ b/avatar-h5-renderer/public/Core/live2dcubismcore.d.ts @@ -0,0 +1,463 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Proprietary Software license + * that can be found at https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html. + */ +declare namespace Live2DCubismCore { + /** Cubism version identifier. */ + type csmVersion = number; + /** moc3 version identifier. */ + type csmMocVersion = number; + /** Parameter type identifier. */ + type csmParameterType = number; + /** Necessary alignment for mocs (in bytes). */ + const AlignofMoc: number; + /** Necessary alignment for models (in bytes). */ + const AlignofModel: number; + /** .moc3 file version Unknown */ + const MocVersion_Unknown: number; + /** .moc3 file version 3.0.00 - 3.2.07 */ + const MocVersion_30: number; + /** .moc3 file version 3.3.00 - 3.3.03 */ + const MocVersion_33: number; + /** .moc3 file version 4.0.00 - 4.1.05 */ + const MocVersion_40: number; + /** .moc3 file version 4.2.00 - 4.2.04 */ + const MocVersion_42: number; + /** .moc3 file version 5.0.00 - 5.2.03 */ + const MocVersion_50: number; + /** .moc3 file version 5.3.00 - */ + const MocVersion_53: number; + /** Normal Parameter. */ + const ParameterType_Normal: number; + /** Parameter for blend shape. */ + const ParameterType_BlendShape: number; + /** Normal blend. */ + const ColorBlendType_Normal: number; + /** Add blend. */ + const ColorBlendType_Add: number; + /** AddGlow blend. */ + const ColorBlendType_AddGlow: number; + /** Darken blend. */ + const ColorBlendType_Darken: number; + /** Multiply blend. */ + const ColorBlendType_Multiply: number; + /** ColorBurn blend. */ + const ColorBlendType_ColorBurn: number; + /** LinearBurn blend. */ + const ColorBlendType_LinearBurn: number; + /** Lighten blend. */ + const ColorBlendType_Lighten: number; + /** Screen blend. */ + const ColorBlendType_Screen: number; + /** ColorDodge blend. */ + const ColorBlendType_ColorDodge: number; + /** Overlay blend. */ + const ColorBlendType_Overlay: number; + /** SoftLight blend. */ + const ColorBlendType_SoftLight: number; + /** HardLight blend. */ + const ColorBlendType_HardLight: number; + /** LinearLight blend. */ + const ColorBlendType_LinearLight: number; + /** Hue blend. */ + const ColorBlendType_Hue: number; + /** Color blend. */ + const ColorBlendType_Color: number; + /** Add compatible blend. */ + const ColorBlendType_AddCompatible: number; + /** Multiply compatible blend. */ + const ColorBlendType_MultiplyCompatible: number; + /** Over blend. */ + const AlphaBlendType_Over: number; + /** Atop blend. */ + const AlphaBlendType_Atop: number; + /** Out blend. */ + const AlphaBlendType_Out: number; + /** ConjointOver blend. */ + const AlphaBlendType_ConjointOver: number; + /** DisjointOver blend. */ + const AlphaBlendType_DisjointOver: number; + /** Log handler. + * + * @param message Null-terminated string message to log. + */ + interface csmLogFunction { + (message: string): void; + } + /** Cubism version. */ + class Version { + /** + * Queries Core version. + * + * @return Core version. + */ + static csmGetVersion(): csmVersion; + /** + * Gets Moc file supported latest version. + * + * @return Moc file latest format version. + */ + static csmGetLatestMocVersion(): csmMocVersion; + /** + * Gets Moc file's format version + * + * @param {Moc} moc Moc + * @param {ArrayBuffer} mocBytes Arraybuffer + * @returns {csmMocVersion} Moc file format version. + */ + static csmGetMocVersion(moc: Moc, mocBytes: ArrayBuffer): csmMocVersion; + /** + * Gets Moc file's format version + * + * @param {ArrayBuffer} mocBytes Moc bytes. + * @returns {csmMocVersion} Moc file format version. + */ + static csmGetMocVersion(mocBytes: ArrayBuffer): csmMocVersion; + private constructor(); + } + /** Cubism logging. */ + class Logging { + private static logFunction; + /** + * Sets log handler. + * + * @param handler Handler to use. + */ + static csmSetLogFunction(handler: csmLogFunction): void; + /** + * Queries log handler. + * + * @return Log handler. + */ + static csmGetLogFunction(): csmLogFunction; + /** + * Wrap log function. + * + * @param messagePtr number + * + * @return string + */ + private static wrapLogFunction; + private constructor(); + } + /** Cubism moc. */ + class Moc { + /** + * Checks consistency of a moc. + * + * @param mocBytes Moc bytes. + * + * @returns '1' if Moc is valid; '0' otherwise. + */ + hasMocConsistency(mocBytes: ArrayBuffer): number; + /** Creates [[Moc]] from [[ArrayBuffer]]. + * + * @param buffer Array buffer + * + * @return [[Moc]] on success; [[null]] otherwise. + */ + static fromArrayBuffer(buffer: ArrayBuffer): Moc; + /** Releases instance. */ + _release(): void; + /** Native moc. */ + _ptr: number; + /** + * Initializes instance. + * + * @param mocBytes Moc bytes. + */ + private constructor(); + } + /** Cubism model. */ + class Model { + /** Parameters. */ + parameters: Parameters; + /** Parts. */ + parts: Parts; + /** Drawables. */ + drawables: Drawables; + /** Offscreen. */ + offscreens: Offscreens; + /** Canvas information. */ + canvasinfo: CanvasInfo; + /** Object render orders. */ + private renderOrders; + /** + * Creates [[Model]] from [[Moc]]. + * + * @param moc Moc + * + * @return [[Model]] on success; [[null]] otherwise. + */ + static fromMoc(moc: Moc): Model; + /** + * Gets object render orders. + * + * @returns {Int32Array} Object render orders. + */ + getRenderOrders(): Int32Array; + /** Updates instance. */ + update(): void; + /** Releases instance. */ + release(): void; + /** Native model. */ + _ptr: number; + /** + * Initializes instance. + * + * @param moc Moc + */ + private constructor(); + } + /** Canvas information interface. */ + class CanvasInfo { + /** Width of native model canvas. */ + CanvasWidth: number; + /** Height of native model canvas. */ + CanvasHeight: number; + /** Coordinate origin of X axis. */ + CanvasOriginX: number; + /** Coordinate origin of Y axis. */ + CanvasOriginY: number; + /** Pixels per unit of native model. */ + PixelsPerUnit: number; + /** + * Initializes instance. + * + * @param modelPtr Native model pointer. + */ + constructor(modelPtr: number); + } + /** Cubism model parameters */ + class Parameters { + /** Parameter count. */ + count: number; + /** Parameter IDs. */ + ids: Array; + /** Minimum parameter values. */ + minimumValues: Float32Array; + /** Parameter types. */ + types: Int32Array; + /** Maximum parameter values. */ + maximumValues: Float32Array; + /** Default parameter values. */ + defaultValues: Float32Array; + /** Parameter values. */ + values: Float32Array; + /** Parameter Repeat informations. */ + repeats: Int32Array; + /** Number of key values of each parameter. */ + keyCounts: Int32Array; + /** Key values of each parameter. */ + keyValues: Array; + /** + * Initializes instance. + * + * @param modelPtr Native model. + */ + constructor(modelPtr: number); + } + /** Cubism model parts */ + class Parts { + /** Part count. */ + count: number; + /** Part IDs. */ + ids: Array; + /** Opacity values. */ + opacities: Float32Array; + /** Part's parent part indices. */ + parentIndices: Int32Array; + /** Part's offscreen indices. If the part does not use an offscreen, the value is '-1'. */ + offscreenIndices: Int32Array; + /** + * Initializes instance. + * + * @param modelPtr Native model. + */ + constructor(modelPtr: number); + } + /** Cubism model drawables */ + class Drawables { + /** Drawable count. */ + count: number; + /** Drawable IDs. */ + ids: Array; + /** Constant drawable flags. */ + constantFlags: Uint8Array; + /** Dynamic drawable flags. */ + dynamicFlags: Uint8Array; + /** Drawable texture indices. */ + textureIndices: Int32Array; + /** Drawable draw orders. */ + drawOrders: Int32Array; + /** Drawable opacities. */ + opacities: Float32Array; + /** Mask count for each drawable. */ + maskCounts: Int32Array; + /** Masks for each drawable. */ + masks: Array; + /** Number of vertices of each drawable. */ + vertexCounts: Int32Array; + /** 2D vertex position data of each drawable. */ + vertexPositions: Array; + /** 2D texture coordinate data of each drawables. */ + vertexUvs: Array; + /** Number of triangle indices for each drawable. */ + indexCounts: Int32Array; + /** Triangle index data for each drawable. */ + indices: Array; + /** Information multiply color. */ + multiplyColors: Float32Array; + /** Information Screen color. */ + screenColors: Float32Array; + /** Indices of drawables parent part. */ + parentPartIndices: Int32Array; + /** Blend modes of drawables. */ + blendModes: Int32Array; + /** Resets all dynamic drawable flags.. */ + resetDynamicFlags(): void; + /** Native model. */ + private _modelPtr; + /** + * Initializes instance. + * + * @param modelPtr Native model. + */ + constructor(modelPtr: number); + } + /** Cubism model offscreens */ + class Offscreens { + /** Number of offscreens. */ + count: number; + /** Offscreen blend modes. */ + blendModes: Int32Array; + /** Offscreen opacities. */ + opacities: Float32Array; + /** Offscreen's owner indices. */ + ownerIndices: Int32Array; + /** Offscreen's multiply colors. */ + multiplyColors: Float32Array; + /** Offscreen's screen colors. */ + screenColors: Float32Array; + /** Offscreen's mask counts. */ + maskCounts: Int32Array; + /** Offscreen's masks. */ + masks: Array; + /** Offscreen's constant flags. */ + constantFlags: Uint8Array; + /** + * Initializes instance. + * + * @param modelPtr Native model. + */ + constructor(modelPtr: number); + } + /** Utility functions. */ + class Utils { + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasBlendAdditiveBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasBlendMultiplicativeBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasIsDoubleSidedBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasIsInvertedMaskBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasIsVisibleBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasVisibilityDidChangeBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasOpacityDidChangeBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasDrawOrderDidChangeBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasRenderOrderDidChangeBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasVertexPositionsDidChangeBit(bitfield: number): boolean; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + static hasBlendColorDidChangeBit(bitfield: number): boolean; + } + /** Memory functions. */ + class Memory { + /** + * HACK: + * Extend memory size allocated during module initialization. + * If the specified size is less than or equal to 16777216(byte), the default of 16 MB is allocated. + * + * @see https://github.com/emscripten-core/emscripten/blob/main/src/settings.js#L161 + * + * @param size allocated memory size [byte(s)] + */ + static initializeAmountOfMemory(size: number): void; + private constructor(); + } + /** Emscripten Cubism Core module. */ +} diff --git a/avatar-h5-renderer/public/Core/live2dcubismcore.js b/avatar-h5-renderer/public/Core/live2dcubismcore.js new file mode 100644 index 0000000..1e4756f --- /dev/null +++ b/avatar-h5-renderer/public/Core/live2dcubismcore.js @@ -0,0 +1,830 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Proprietary Software license + * that can be found at https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html. + */ +var Live2DCubismCore; +(function (Live2DCubismCore) { + /** C calls. */ + var _csm = /** @class */ (function () { + function _csm() { + } + _csm.getVersion = function () { + return _em.ccall("csmGetVersion", "number", [], []); + }; + _csm.getLatestMocVersion = function () { + return _em.ccall("csmGetLatestMocVersion", "number", [], []); + }; + _csm.getMocVersion = function (moc, mocSize) { + return _em.ccall("csmGetMocVersion", "number", ["number", "number"], [moc, mocSize]); + }; + _csm.getLogFunction = function () { + return _em.ccall("csmGetLogFunction", "number", [], []); + }; + _csm.getSizeofModel = function (moc) { + return _em.ccall("csmGetSizeofModel", "number", ["number"], [moc]); + }; + _csm.reviveMocInPlace = function (memory, mocSize) { + return _em.ccall("csmReviveMocInPlace", "number", ["number", "number"], [memory, mocSize]); + }; + _csm.initializeModelInPlace = function (moc, memory, modelSize) { + return _em.ccall("csmInitializeModelInPlace", "number", ["number", "number", "number"], [moc, memory, modelSize]); + }; + _csm.hasMocConsistency = function (memory, mocSize) { + return _em.ccall("csmHasMocConsistency", "number", ["number", "number"], [memory, mocSize]); + }; + _csm.getRenderOrders = function (model) { + return _em.ccall("csmGetRenderOrders", "number", ["number"], [model]); + }; + _csm.getParameterCount = function (model) { + return _em.ccall("csmGetParameterCount", "number", ["number"], [model]); + }; + _csm.getParameterIds = function (model) { + return _em.ccall("csmGetParameterIds", "number", ["number"], [model]); + }; + _csm.getParameterMinimumValues = function (model) { + return _em.ccall("csmGetParameterMinimumValues", "number", ["number"], [model]); + }; + _csm.getParameterTypes = function (model) { + return _em.ccall("csmGetParameterTypes", "number", ["number"], [model]); + }; + _csm.getParameterMaximumValues = function (model) { + return _em.ccall("csmGetParameterMaximumValues", "number", ["number"], [model]); + }; + _csm.getParameterDefaultValues = function (model) { + return _em.ccall("csmGetParameterDefaultValues", "number", ["number"], [model]); + }; + _csm.getParameterValues = function (model) { + return _em.ccall("csmGetParameterValues", "number", ["number"], [model]); + }; + _csm.getParameterRepeats = function (model) { + return _em.ccall("csmGetParameterRepeats", "number", ["number"], [model]); + }; + _csm.getParameterKeyCounts = function (model) { + return _em.ccall("csmGetParameterKeyCounts", "number", ["number"], [model]); + }; + _csm.getParameterKeyValues = function (model) { + return _em.ccall("csmGetParameterKeyValues", "number", ["number"], [model]); + }; + _csm.getPartCount = function (model) { + return _em.ccall("csmGetPartCount", "number", ["number"], [model]); + }; + _csm.getPartIds = function (model) { + return _em.ccall("csmGetPartIds", "number", ["number"], [model]); + }; + _csm.getPartOpacities = function (model) { + return _em.ccall("csmGetPartOpacities", "number", ["number"], [model]); + }; + _csm.getPartParentPartIndices = function (model) { + return _em.ccall("csmGetPartParentPartIndices", "number", ["number"], [model]); + }; + _csm.getPartOffscreenIndices = function (model) { + return _em.ccall("csmGetPartOffscreenIndices", "number", ["number"], [model]); + }; + _csm.getDrawableCount = function (model) { + return _em.ccall("csmGetDrawableCount", "number", ["number"], [model]); + }; + _csm.getDrawableIds = function (model) { + return _em.ccall("csmGetDrawableIds", "number", ["number"], [model]); + }; + _csm.getDrawableConstantFlags = function (model) { + return _em.ccall("csmGetDrawableConstantFlags", "number", ["number"], [model]); + }; + _csm.getDrawableDynamicFlags = function (model) { + return _em.ccall("csmGetDrawableDynamicFlags", "number", ["number"], [model]); + }; + _csm.getDrawableTextureIndices = function (model) { + return _em.ccall("csmGetDrawableTextureIndices", "number", ["number"], [model]); + }; + _csm.getDrawableDrawOrders = function (model) { + return _em.ccall("csmGetDrawableDrawOrders", "number", ["number"], [model]); + }; + _csm.getDrawableOpacities = function (model) { + return _em.ccall("csmGetDrawableOpacities", "number", ["number"], [model]); + }; + _csm.getDrawableMaskCounts = function (model) { + return _em.ccall("csmGetDrawableMaskCounts", "number", ["number"], [model]); + }; + _csm.getDrawableMasks = function (model) { + return _em.ccall("csmGetDrawableMasks", "number", ["number"], [model]); + }; + _csm.getDrawableVertexCounts = function (model) { + return _em.ccall("csmGetDrawableVertexCounts", "number", ["number"], [model]); + }; + _csm.getDrawableVertexPositions = function (model) { + return _em.ccall("csmGetDrawableVertexPositions", "number", ["number"], [model]); + }; + _csm.getDrawableVertexUvs = function (model) { + return _em.ccall("csmGetDrawableVertexUvs", "number", ["number"], [model]); + }; + _csm.getDrawableIndexCounts = function (model) { + return _em.ccall("csmGetDrawableIndexCounts", "number", ["number"], [model]); + }; + _csm.getDrawableIndices = function (model) { + return _em.ccall("csmGetDrawableIndices", "number", ["number"], [model]); + }; + _csm.getDrawableMultiplyColors = function (model) { + return _em.ccall("csmGetDrawableMultiplyColors", "number", ["number"], [model]); + }; + _csm.getDrawableScreenColors = function (model) { + return _em.ccall("csmGetDrawableScreenColors", "number", ["number"], [model]); + }; + _csm.getDrawableParentPartIndices = function (model) { + return _em.ccall("csmGetDrawableParentPartIndices", "number", ["number"], [model]); + }; + _csm.getDrawableBlendModes = function (model) { + return _em.ccall("csmGetDrawableBlendModes", "number", ["number"], [model]); + }; + _csm.getOffscreenCount = function (model) { + return _em.ccall("csmGetOffscreenCount", "number", ["number"], [model]); + }; + _csm.getOffscreenBlendModes = function (model) { + return _em.ccall("csmGetOffscreenBlendModes", "number", ["number"], [model]); + }; + _csm.getOffscreenOpacities = function (model) { + return _em.ccall("csmGetOffscreenOpacities", "number", ["number"], [model]); + }; + _csm.getOffscreenOwnerIndices = function (model) { + return _em.ccall("csmGetOffscreenOwnerIndices", "number", ["number"], [model]); + }; + _csm.getOffscreenMultiplyColors = function (model) { + return _em.ccall("csmGetOffscreenMultiplyColors", "number", ["number"], [model]); + }; + _csm.getOffscreenScreenColors = function (model) { + return _em.ccall("csmGetOffscreenScreenColors", "number", ["number"], [model]); + }; + _csm.getOffscreenMaskCounts = function (model) { + return _em.ccall("csmGetOffscreenMaskCounts", "number", ["number"], [model]); + }; + _csm.getOffscreenMasks = function (model) { + return _em.ccall("csmGetOffscreenMasks", "number", ["number"], [model]); + }; + _csm.getOffscreenConstantFlags = function (model) { + return _em.ccall("csmGetOffscreenConstantFlags", "number", ["number"], [model]); + }; + _csm.mallocMoc = function (mocSize) { + return _em.ccall("csmMallocMoc", "number", ["number"], [mocSize]); + }; + _csm.mallocModelAndInitialize = function (moc) { + return _em.ccall("csmMallocModelAndInitialize", "number", ["number"], [moc]); + }; + _csm.malloc = function (size) { + return _em.ccall("csmMalloc", "number", ["number"], [size]); + }; + _csm.setLogFunction = function (handler) { + _em.ccall("csmSetLogFunction", null, ["number"], [handler]); + }; + _csm.updateModel = function (model) { + _em.ccall("csmUpdateModel", null, ["number"], [model]); + }; + _csm.readCanvasInfo = function (model, outSizeInPixels, outOriginInPixels, outPixelsPerUnit) { + _em.ccall("csmReadCanvasInfo", null, ["number", "number", "number", "number"], [model, outSizeInPixels, outOriginInPixels, outPixelsPerUnit]); + }; + _csm.resetDrawableDynamicFlags = function (model) { + _em.ccall("csmResetDrawableDynamicFlags", null, ["number"], [model]); + }; + _csm.free = function (memory) { + _em.ccall("csmFree", null, ["number"], [memory]); + }; + _csm.initializeAmountOfMemory = function (size) { + _em.ccall("csmInitializeAmountOfMemory", null, ["number"], [size]); + }; + return _csm; + }()); + /** Necessary alignment for mocs (in bytes). */ + Live2DCubismCore.AlignofMoc = 64; + /** Necessary alignment for models (in bytes). */ + Live2DCubismCore.AlignofModel = 16; + /** .moc3 file version Unknown */ + Live2DCubismCore.MocVersion_Unknown = 0; + /** .moc3 file version 3.0.00 - 3.2.07 */ + Live2DCubismCore.MocVersion_30 = 1; + /** .moc3 file version 3.3.00 - 3.3.03 */ + Live2DCubismCore.MocVersion_33 = 2; + /** .moc3 file version 4.0.00 - 4.1.05 */ + Live2DCubismCore.MocVersion_40 = 3; + /** .moc3 file version 4.2.00 - 4.2.04 */ + Live2DCubismCore.MocVersion_42 = 4; + /** .moc3 file version 5.0.00 - 5.2.03 */ + Live2DCubismCore.MocVersion_50 = 5; + /** .moc3 file version 5.3.00 - */ + Live2DCubismCore.MocVersion_53 = 6; + /** Normal Parameter. */ + Live2DCubismCore.ParameterType_Normal = 0; + /** Parameter for blend shape. */ + Live2DCubismCore.ParameterType_BlendShape = 1; + /** Normal blend. */ + Live2DCubismCore.ColorBlendType_Normal = 0; + /** Add blend. */ + Live2DCubismCore.ColorBlendType_Add = 3; + /** AddGlow blend. */ + Live2DCubismCore.ColorBlendType_AddGlow = 4; + /** Darken blend. */ + Live2DCubismCore.ColorBlendType_Darken = 5; + /** Multiply blend. */ + Live2DCubismCore.ColorBlendType_Multiply = 6; + /** ColorBurn blend. */ + Live2DCubismCore.ColorBlendType_ColorBurn = 7; + /** LinearBurn blend. */ + Live2DCubismCore.ColorBlendType_LinearBurn = 8; + /** Lighten blend. */ + Live2DCubismCore.ColorBlendType_Lighten = 9; + /** Screen blend. */ + Live2DCubismCore.ColorBlendType_Screen = 10; + /** ColorDodge blend. */ + Live2DCubismCore.ColorBlendType_ColorDodge = 11; + /** Overlay blend. */ + Live2DCubismCore.ColorBlendType_Overlay = 12; + /** SoftLight blend. */ + Live2DCubismCore.ColorBlendType_SoftLight = 13; + /** HardLight blend. */ + Live2DCubismCore.ColorBlendType_HardLight = 14; + /** LinearLight blend. */ + Live2DCubismCore.ColorBlendType_LinearLight = 15; + /** Hue blend. */ + Live2DCubismCore.ColorBlendType_Hue = 16; + /** Color blend. */ + Live2DCubismCore.ColorBlendType_Color = 17; + /** Add compatible blend. */ + Live2DCubismCore.ColorBlendType_AddCompatible = 1; + /** Multiply compatible blend. */ + Live2DCubismCore.ColorBlendType_MultiplyCompatible = 2; + /** Over blend. */ + Live2DCubismCore.AlphaBlendType_Over = 0; + /** Atop blend. */ + Live2DCubismCore.AlphaBlendType_Atop = 1; + /** Out blend. */ + Live2DCubismCore.AlphaBlendType_Out = 2; + /** ConjointOver blend. */ + Live2DCubismCore.AlphaBlendType_ConjointOver = 3; + /** DisjointOver blend. */ + Live2DCubismCore.AlphaBlendType_DisjointOver = 4; + ; + /** Cubism version. */ + var Version = /** @class */ (function () { + function Version() { + } + /** + * Queries Core version. + * + * @return Core version. + */ + Version.csmGetVersion = function () { + return _csm.getVersion(); + }; + /** + * Gets Moc file supported latest version. + * + * @return Moc file latest format version. + */ + Version.csmGetLatestMocVersion = function () { + return _csm.getLatestMocVersion(); + }; + Version.csmGetMocVersion = function (data, mocBytes) { + if (data instanceof Moc) { + return _csm.getMocVersion(data._ptr, mocBytes.byteLength); + } + // Allocate memory. + var memory = _csm.mallocMoc(data.byteLength); + if (!memory) { + return 0; + } + // Initialize memory. + var destination = new Uint8Array(_em.HEAPU8.buffer, memory, data.byteLength); + destination.set(new Uint8Array(data)); + var mocVersion = _csm.getMocVersion(memory, data.byteLength); + _csm.free(memory); + return mocVersion; + }; + return Version; + }()); + Live2DCubismCore.Version = Version; + /** Cubism logging. */ + var Logging = /** @class */ (function () { + function Logging() { + } + /** + * Sets log handler. + * + * @param handler Handler to use. + */ + Logging.csmSetLogFunction = function (handler) { + // Cache log handler. + Logging.logFunction = handler; + // Wrap function to pointer. + var pointer = _em.addFunction(Logging.wrapLogFunction, 'vi'); + // Sets log handler. + _csm.setLogFunction(pointer); + }; + /** + * Queries log handler. + * + * @return Log handler. + */ + Logging.csmGetLogFunction = function () { + return Logging.logFunction; + }; + /** + * Wrap log function. + * + * @param messagePtr number + * + * @return string + */ + Logging.wrapLogFunction = function (messagePtr) { + // Pointer to string. + var messageStr = _em.UTF8ToString(messagePtr); + // Run log function. + Logging.logFunction(messageStr); + }; + return Logging; + }()); + Live2DCubismCore.Logging = Logging; + /** Cubism moc. */ + var Moc = /** @class */ (function () { + /** + * Initializes instance. + * + * @param mocBytes Moc bytes. + */ + function Moc(mocBytes) { + // Allocate memory. + var memory = _csm.mallocMoc(mocBytes.byteLength); + if (!memory) { + return; + } + // Initialize memory. + var destination = new Uint8Array(_em.HEAPU8.buffer, memory, mocBytes.byteLength); + destination.set(new Uint8Array(mocBytes)); + // Revive moc. + this._ptr = _csm.reviveMocInPlace(memory, mocBytes.byteLength); + if (!this._ptr) { + _csm.free(memory); + } + } + /** + * Checks consistency of a moc. + * + * @param mocBytes Moc bytes. + * + * @returns '1' if Moc is valid; '0' otherwise. + */ + Moc.prototype.hasMocConsistency = function (mocBytes) { + // Allocate memory. + var memory = _csm.mallocMoc(mocBytes.byteLength); + if (!memory) { + return; + } + // Initialize memory. + var destination = new Uint8Array(_em.HEAPU8.buffer, memory, mocBytes.byteLength); + destination.set(new Uint8Array(mocBytes)); + var hasConsistency = _csm.hasMocConsistency(memory, mocBytes.byteLength); + _csm.free(memory); + return hasConsistency; + }; + /** Creates [[Moc]] from [[ArrayBuffer]]. + * + * @param buffer Array buffer + * + * @return [[Moc]] on success; [[null]] otherwise. + */ + Moc.fromArrayBuffer = function (buffer) { + if (!buffer) { + return null; + } + var moc = new Moc(buffer); + return (moc._ptr) + ? moc + : null; + }; + /** Releases instance. */ + Moc.prototype._release = function () { + _csm.free(this._ptr); + this._ptr = 0; + }; + return Moc; + }()); + Live2DCubismCore.Moc = Moc; + /** Cubism model. */ + var Model = /** @class */ (function () { + /** + * Initializes instance. + * + * @param moc Moc + */ + function Model(moc) { + this._ptr = _csm.mallocModelAndInitialize(moc._ptr); + if (!this._ptr) { + return; + } + this.parameters = new Parameters(this._ptr); + this.parts = new Parts(this._ptr); + this.drawables = new Drawables(this._ptr); + this.offscreens = new Offscreens(this._ptr); + this.canvasinfo = new CanvasInfo(this._ptr); + var length = _csm.getDrawableCount(this._ptr) + _csm.getOffscreenCount(this._ptr); + this.renderOrders = new Int32Array(_em.HEAP32.buffer, _csm.getRenderOrders(this._ptr), length); + } + /** + * Creates [[Model]] from [[Moc]]. + * + * @param moc Moc + * + * @return [[Model]] on success; [[null]] otherwise. + */ + Model.fromMoc = function (moc) { + var model = new Model(moc); + return (model._ptr) + ? model + : null; + }; + /** + * Gets object render orders. + * + * @returns {Int32Array} Object render orders. + */ + Model.prototype.getRenderOrders = function () { + return this.renderOrders; + }; + /** Updates instance. */ + Model.prototype.update = function () { + _csm.updateModel(this._ptr); + }; + /** Releases instance. */ + Model.prototype.release = function () { + _csm.free(this._ptr); + this._ptr = 0; + }; + return Model; + }()); + Live2DCubismCore.Model = Model; + /** Canvas information interface. */ + var CanvasInfo = /** @class */ (function () { + /** + * Initializes instance. + * + * @param modelPtr Native model pointer. + */ + function CanvasInfo(modelPtr) { + if (!modelPtr) { + return; + } + // Preserve the pointer ant heap for get data throw args. + var _canvasSize_data = new Float32Array(2); + var _canvasSize_nDataBytes = _canvasSize_data.length * _canvasSize_data.BYTES_PER_ELEMENT; + var _canvasSize_dataPtr = _csm.malloc(_canvasSize_nDataBytes); + var _canvasSize_dataHeap = new Uint8Array(_em.HEAPU8.buffer, _canvasSize_dataPtr, _canvasSize_nDataBytes); + _canvasSize_dataHeap.set(new Uint8Array(_canvasSize_data.buffer)); + var _canvasOrigin_data = new Float32Array(2); + var _canvasOrigin_nDataBytes = _canvasOrigin_data.length * _canvasOrigin_data.BYTES_PER_ELEMENT; + var _canvasOrigin_dataPtr = _csm.malloc(_canvasOrigin_nDataBytes); + var _canvasOrigin_dataHeap = new Uint8Array(_em.HEAPU8.buffer, _canvasOrigin_dataPtr, _canvasOrigin_nDataBytes); + _canvasOrigin_dataHeap.set(new Uint8Array(_canvasOrigin_data.buffer)); + var _canvasPPU_data = new Float32Array(1); + var _canvasPPU_nDataBytes = _canvasPPU_data.length * _canvasPPU_data.BYTES_PER_ELEMENT; + var _canvasPPU_dataPtr = _csm.malloc(_canvasPPU_nDataBytes); + var _canvasPPU_dataHeap = new Uint8Array(_em.HEAPU8.buffer, _canvasPPU_dataPtr, _canvasPPU_nDataBytes); + _canvasPPU_dataHeap.set(new Uint8Array(_canvasPPU_data.buffer)); + // Call function and get result + _csm.readCanvasInfo(modelPtr, _canvasSize_dataHeap.byteOffset, _canvasOrigin_dataHeap.byteOffset, _canvasPPU_dataHeap.byteOffset); + _canvasSize_data = new Float32Array(_canvasSize_dataHeap.buffer, _canvasSize_dataHeap.byteOffset, _canvasSize_dataHeap.length); + _canvasOrigin_data = new Float32Array(_canvasOrigin_dataHeap.buffer, _canvasOrigin_dataHeap.byteOffset, _canvasOrigin_dataHeap.length); + _canvasPPU_data = new Float32Array(_canvasPPU_dataHeap.buffer, _canvasPPU_dataHeap.byteOffset, _canvasPPU_dataHeap.length); + this.CanvasWidth = _canvasSize_data[0]; + this.CanvasHeight = _canvasSize_data[1]; + this.CanvasOriginX = _canvasOrigin_data[0]; + this.CanvasOriginY = _canvasOrigin_data[1]; + this.PixelsPerUnit = _canvasPPU_data[0]; + // Free heap memory + _csm.free(_canvasSize_dataHeap.byteOffset); + _csm.free(_canvasOrigin_dataHeap.byteOffset); + _csm.free(_canvasPPU_dataHeap.byteOffset); + } + return CanvasInfo; + }()); + Live2DCubismCore.CanvasInfo = CanvasInfo; + /** Cubism model parameters */ + var Parameters = /** @class */ (function () { + /** + * Initializes instance. + * + * @param modelPtr Native model. + */ + function Parameters(modelPtr) { + var length = 0; + var length2 = null; + this.count = _csm.getParameterCount(modelPtr); + length = _csm.getParameterCount(modelPtr); + this.ids = new Array(length); + var _ids = new Uint32Array(_em.HEAPU32.buffer, _csm.getParameterIds(modelPtr), length); + for (var i = 0; i < _ids.length; i++) { + this.ids[i] = _em.UTF8ToString(_ids[i]); + } + length = _csm.getParameterCount(modelPtr); + this.minimumValues = new Float32Array(_em.HEAPF32.buffer, _csm.getParameterMinimumValues(modelPtr), length); + length = _csm.getParameterCount(modelPtr); + this.types = new Int32Array(_em.HEAP32.buffer, _csm.getParameterTypes(modelPtr), length); + length = _csm.getParameterCount(modelPtr); + this.maximumValues = new Float32Array(_em.HEAPF32.buffer, _csm.getParameterMaximumValues(modelPtr), length); + length = _csm.getParameterCount(modelPtr); + this.defaultValues = new Float32Array(_em.HEAPF32.buffer, _csm.getParameterDefaultValues(modelPtr), length); + length = _csm.getParameterCount(modelPtr); + this.values = new Float32Array(_em.HEAPF32.buffer, _csm.getParameterValues(modelPtr), length); + length = _csm.getParameterCount(modelPtr); + this.repeats = new Int32Array(_em.HEAP32.buffer, _csm.getParameterRepeats(modelPtr), length); + length = _csm.getParameterCount(modelPtr); + this.keyCounts = new Int32Array(_em.HEAP32.buffer, _csm.getParameterKeyCounts(modelPtr), length); + length = _csm.getParameterCount(modelPtr); + length2 = new Int32Array(_em.HEAP32.buffer, _csm.getParameterKeyCounts(modelPtr), length); + this.keyValues = new Array(length); + var _keyValues = new Uint32Array(_em.HEAPU32.buffer, _csm.getParameterKeyValues(modelPtr), length); + for (var i = 0; i < _keyValues.length; i++) { + this.keyValues[i] = new Float32Array(_em.HEAPF32.buffer, _keyValues[i], length2[i]); + } + } + return Parameters; + }()); + Live2DCubismCore.Parameters = Parameters; + /** Cubism model parts */ + var Parts = /** @class */ (function () { + /** + * Initializes instance. + * + * @param modelPtr Native model. + */ + function Parts(modelPtr) { + var length = 0; + this.count = _csm.getPartCount(modelPtr); + length = _csm.getPartCount(modelPtr); + this.ids = new Array(length); + var _ids = new Uint32Array(_em.HEAPU32.buffer, _csm.getPartIds(modelPtr), length); + for (var i = 0; i < _ids.length; i++) { + this.ids[i] = _em.UTF8ToString(_ids[i]); + } + length = _csm.getPartCount(modelPtr); + this.opacities = new Float32Array(_em.HEAPF32.buffer, _csm.getPartOpacities(modelPtr), length); + length = _csm.getPartCount(modelPtr); + this.parentIndices = new Int32Array(_em.HEAP32.buffer, _csm.getPartParentPartIndices(modelPtr), length); + length = _csm.getPartCount(modelPtr); + this.offscreenIndices = new Int32Array(_em.HEAP32.buffer, _csm.getPartOffscreenIndices(modelPtr), length); + } + return Parts; + }()); + Live2DCubismCore.Parts = Parts; + /** Cubism model drawables */ + var Drawables = /** @class */ (function () { + /** + * Initializes instance. + * + * @param modelPtr Native model. + */ + function Drawables(modelPtr) { + this._modelPtr = modelPtr; + var length = 0; + var length2 = null; + this.count = _csm.getDrawableCount(modelPtr); + length = _csm.getDrawableCount(modelPtr); + this.ids = new Array(length); + var _ids = new Uint32Array(_em.HEAPU32.buffer, _csm.getDrawableIds(modelPtr), length); + for (var i = 0; i < _ids.length; i++) { + this.ids[i] = _em.UTF8ToString(_ids[i]); + } + length = _csm.getDrawableCount(modelPtr); + this.constantFlags = new Uint8Array(_em.HEAPU8.buffer, _csm.getDrawableConstantFlags(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.dynamicFlags = new Uint8Array(_em.HEAPU8.buffer, _csm.getDrawableDynamicFlags(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.textureIndices = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableTextureIndices(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.drawOrders = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableDrawOrders(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.opacities = new Float32Array(_em.HEAPF32.buffer, _csm.getDrawableOpacities(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.maskCounts = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableMaskCounts(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.vertexCounts = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableVertexCounts(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.indexCounts = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableIndexCounts(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.multiplyColors = new Float32Array(_em.HEAPF32.buffer, _csm.getDrawableMultiplyColors(modelPtr), length * 4); + length = _csm.getDrawableCount(modelPtr); + this.screenColors = new Float32Array(_em.HEAPF32.buffer, _csm.getDrawableScreenColors(modelPtr), length * 4); + length = _csm.getDrawableCount(modelPtr); + this.parentPartIndices = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableParentPartIndices(modelPtr), length); + length = _csm.getDrawableCount(modelPtr); + this.blendModes = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableBlendModes(modelPtr), length * 2); + length = _csm.getDrawableCount(modelPtr); + length2 = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableMaskCounts(modelPtr), length); + this.masks = new Array(length); + var _masks = new Uint32Array(_em.HEAPU32.buffer, _csm.getDrawableMasks(modelPtr), length); + for (var i = 0; i < _masks.length; i++) { + this.masks[i] = new Int32Array(_em.HEAP32.buffer, _masks[i], length2[i]); + } + length = _csm.getDrawableCount(modelPtr); + length2 = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableVertexCounts(modelPtr), length); + this.vertexPositions = new Array(length); + var _vertexPositions = new Uint32Array(_em.HEAPU32.buffer, _csm.getDrawableVertexPositions(modelPtr), length); + for (var i = 0; i < _vertexPositions.length; i++) { + this.vertexPositions[i] = new Float32Array(_em.HEAPF32.buffer, _vertexPositions[i], length2[i] * 2); + } + length = _csm.getDrawableCount(modelPtr); + length2 = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableVertexCounts(modelPtr), length); + this.vertexUvs = new Array(length); + var _vertexUvs = new Uint32Array(_em.HEAPU32.buffer, _csm.getDrawableVertexUvs(modelPtr), length); + for (var i = 0; i < _vertexUvs.length; i++) { + this.vertexUvs[i] = new Float32Array(_em.HEAPF32.buffer, _vertexUvs[i], length2[i] * 2); + } + length = _csm.getDrawableCount(modelPtr); + length2 = new Int32Array(_em.HEAP32.buffer, _csm.getDrawableIndexCounts(modelPtr), length); + this.indices = new Array(length); + var _indices = new Uint32Array(_em.HEAPU32.buffer, _csm.getDrawableIndices(modelPtr), length); + for (var i = 0; i < _indices.length; i++) { + this.indices[i] = new Uint16Array(_em.HEAPU16.buffer, _indices[i], length2[i]); + } + } + /** Resets all dynamic drawable flags.. */ + Drawables.prototype.resetDynamicFlags = function () { + _csm.resetDrawableDynamicFlags(this._modelPtr); + }; + return Drawables; + }()); + Live2DCubismCore.Drawables = Drawables; + /** Cubism model offscreens */ + var Offscreens = /** @class */ (function () { + /** + * Initializes instance. + * + * @param modelPtr Native model. + */ + function Offscreens(modelPtr) { + var length = 0; + var length2 = null; + this.count = _csm.getOffscreenCount(modelPtr); + length = _csm.getOffscreenCount(modelPtr); + this.blendModes = new Int32Array(_em.HEAP32.buffer, _csm.getOffscreenBlendModes(modelPtr), length * 2); + length = _csm.getOffscreenCount(modelPtr); + this.opacities = new Float32Array(_em.HEAPF32.buffer, _csm.getOffscreenOpacities(modelPtr), length); + length = _csm.getOffscreenCount(modelPtr); + this.ownerIndices = new Int32Array(_em.HEAP32.buffer, _csm.getOffscreenOwnerIndices(modelPtr), length); + length = _csm.getOffscreenCount(modelPtr); + this.multiplyColors = new Float32Array(_em.HEAPF32.buffer, _csm.getOffscreenMultiplyColors(modelPtr), length * 4); + length = _csm.getOffscreenCount(modelPtr); + this.screenColors = new Float32Array(_em.HEAPF32.buffer, _csm.getOffscreenScreenColors(modelPtr), length * 4); + length = _csm.getOffscreenCount(modelPtr); + this.maskCounts = new Int32Array(_em.HEAP32.buffer, _csm.getOffscreenMaskCounts(modelPtr), length); + length = _csm.getOffscreenCount(modelPtr); + this.constantFlags = new Uint8Array(_em.HEAPU8.buffer, _csm.getOffscreenConstantFlags(modelPtr), length); + length = _csm.getOffscreenCount(modelPtr); + length2 = new Int32Array(_em.HEAP32.buffer, _csm.getOffscreenMaskCounts(modelPtr), length); + this.masks = new Array(length); + var _masks = new Uint32Array(_em.HEAPU32.buffer, _csm.getOffscreenMasks(modelPtr), length); + for (var i = 0; i < _masks.length; i++) { + this.masks[i] = new Int32Array(_em.HEAP32.buffer, _masks[i], length2[i]); + } + } + return Offscreens; + }()); + Live2DCubismCore.Offscreens = Offscreens; + /** Utility functions. */ + var Utils = /** @class */ (function () { + function Utils() { + } + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasBlendAdditiveBit = function (bitfield) { + return (bitfield & (1 << 0)) == (1 << 0); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasBlendMultiplicativeBit = function (bitfield) { + return (bitfield & (1 << 1)) == (1 << 1); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasIsDoubleSidedBit = function (bitfield) { + return (bitfield & (1 << 2)) == (1 << 2); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasIsInvertedMaskBit = function (bitfield) { + return (bitfield & (1 << 3)) == (1 << 3); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasIsVisibleBit = function (bitfield) { + return (bitfield & (1 << 0)) == (1 << 0); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasVisibilityDidChangeBit = function (bitfield) { + return (bitfield & (1 << 1)) == (1 << 1); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasOpacityDidChangeBit = function (bitfield) { + return (bitfield & (1 << 2)) == (1 << 2); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasDrawOrderDidChangeBit = function (bitfield) { + return (bitfield & (1 << 3)) == (1 << 3); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasRenderOrderDidChangeBit = function (bitfield) { + return (bitfield & (1 << 4)) == (1 << 4); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasVertexPositionsDidChangeBit = function (bitfield) { + return (bitfield & (1 << 5)) == (1 << 5); + }; + /** + * Checks whether flag is set in bitfield. + * + * @param bitfield Bitfield to query against. + * + * @return [[true]] if bit set; [[false]] otherwise + */ + Utils.hasBlendColorDidChangeBit = function (bitfield) { + return (bitfield & (1 << 6)) == (1 << 6); + }; + return Utils; + }()); + Live2DCubismCore.Utils = Utils; + /** Memory functions. */ + var Memory = /** @class */ (function () { + function Memory() { + } + /** + * HACK: + * Extend memory size allocated during module initialization. + * If the specified size is less than or equal to 16777216(byte), the default of 16 MB is allocated. + * + * @see https://github.com/emscripten-core/emscripten/blob/main/src/settings.js#L161 + * + * @param size allocated memory size [byte(s)] + */ + Memory.initializeAmountOfMemory = function (size) { + if (size > 16777216) { + _csm.initializeAmountOfMemory(size); + } + }; + return Memory; + }()); + Live2DCubismCore.Memory = Memory; + /** Emscripten Cubism Core module. */ + var _em_module=function(){var _scriptDir="undefined"!=typeof document&&document.currentScript?document.currentScript.src:void 0;return function(_em_module){_em_module=_em_module||{};var a,n,l={};for(n in a=a||(void 0!==_em_module?_em_module:{}))a.hasOwnProperty(n)&&(l[n]=a[n]);var x,y,v,w,p=!1,q=!1,r=!1,p="object"==typeof window,q="function"==typeof importScripts,r="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node&&!p&&!q,t=!p&&!r&&!q,u="",D=(r?(u=__dirname+"/",v=function(b,c){var d=z(b);return d||(x=x||require("fs"),b=(y=y||require("path")).normalize(b),d=x.readFileSync(b)),c?d:d.toString()},w=function(b){return assert((b=(b=v(b,!0)).buffer?b:new Uint8Array(b)).buffer),b},1>2]+(Mn<<5)|0)>>2],Qn=q[a- -64>>2]+w(Xn,24)|0,Mn=(lo=q[Qn+8>>2])+-1|0,yo=(ro=q[Qn+4>>2])+-1|0,zo=vo=(Xn=q[q[a+156>>2]+(Xn<<2)>>2])+(lo<<3)|0,Ao=wo=Xn+((uo=w(ro,mo=lo+1|0))<<3)|0,Bo=xo=Xn+(lo+uo<<3)|0,Jo=q[Qn+12>>2],so=x(0|ro),to=x(0|lo),a=0;;)if(Wn=u[(Qn=(po=a<<3)+Nn|0)+4>>2],Sn=x(Wn*so),Yn=u[Qn>>2],Rn=x(Yn*to),Qn=Wn>=x(1),Sn=!(Wn=x(1)|Yn>2],Co=u[Xn+4>>2],Zn=x(bo-Co),Do=u[4+zo>>2],Eo=u[4+Ao>>2],_n=x(Do-Eo),co=x(x(Zn-_n)*x(.5)),Fo=u[xo>>2],Go=u[Xn>>2],$n=x(Fo-Go),Ho=u[vo>>2],Io=u[wo>>2],ao=x(Ho-Io),eo=x(x($n-ao)*x(.5)),_n=x(x(_n+Zn)*x(.5)),ao=x(x(ao+$n)*x(.5)),Ko=1,Zn=x(x(x(x(x(Co+Do)+Eo)+bo)*x(.25))-x(Zn*x(.5))),$n=x(x(x(x(x(Go+Ho)+Io)+Fo)*x(.25))-x($n*x(.5)))),Wnx(-2)^1|(Ynx(-2)^1)?(u[On+po>>2]=x(Wn*eo)+x(x(Yn*ao)+$n),Rn=x(Wn*co),x(x(Yn*_n)+Zn)):(Yn<=x(0)?Wn<=x(0)?(Vn=x(x(Wn+x(2))*x(.5)),Un=x(x(Yn+x(2))*x(.5)),Rn=x(co+co),no=x(Zn-Rn),Sn=x(eo+eo),oo=x($n-Sn),jo=x(Zn-x(_n+_n)),fo=x(jo-Rn),ko=x($n-x(ao+ao)),go=x(ko-Sn),ho=u[Xn+4>>2],io=u[Xn>>2]):Qn?(Rn=x(co*x(3)),Sn=x(Zn-x(_n+_n)),jo=x(Rn+Sn),fo=x(eo*x(3)),go=x($n-x(ao+ao)),ko=x(fo+go),Vn=x(x(Wn+x(-1))*x(.5)),Un=x(x(Yn+x(2))*x(.5)),ho=x(Rn+Zn),io=x(fo+$n),fo=x(co+Sn),go=x(eo+go),no=u[4+Ao>>2],oo=u[wo>>2]):(Rn=x(Zn-x(_n+_n)),Qn=yo,Tn=x(y(Sn))>2],oo=u[Qn>>2],Qn=Xn+(w(Tn,mo)<<3)|0,ho=u[Qn+4>>2],io=u[Qn>>2]):Yn>=x(1)?Wn<=x(0)?(Vn=x(x(Wn+x(2))*x(.5)),Un=x(x(Yn+x(-1))*x(.5)),Rn=x(co+co),fo=x(x(_n+Zn)-Rn),Sn=x(eo+eo),go=x(x(ao+$n)-Sn),ho=x(x(_n*x(3))+Zn),no=x(ho-Rn),io=x(x(ao*x(3))+$n),oo=x(io-Sn),jo=u[4+zo>>2],ko=u[vo>>2]):Qn?(Rn=x(co*x(3)),jo=x(Rn+x(_n+Zn)),Sn=x(eo*x(3)),ko=x(Sn+x(ao+$n)),bo=Rn,Rn=x(x(_n*x(3))+Zn),ho=x(bo+Rn),bo=Sn,Sn=x(x(ao*x(3))+$n),io=x(bo+Sn),Vn=x(x(Wn+x(-1))*x(.5)),Un=x(x(Yn+x(-1))*x(.5)),no=x(co+Rn),oo=x(eo+Sn),fo=u[4+Bo>>2],go=u[xo>>2]):(Rn=x(x(_n*x(3))+Zn),Qn=yo,Tn=x(y(Sn))>2],go=u[Qn>>2],Qn=Xn+(w(Tn,mo)+lo<<3)|0,jo=u[Qn+4>>2],ko=u[Qn>>2]):Wn<=x(0)?(Vn=x(x(Wn+x(2))*x(.5)),Qn=Mn,Tn=x(y(Sn=Rn))>2],ko=u[Qn>>2],ho=u[(Qn=Xn+(Tn<<3)|0)+4>>2],io=u[Qn>>2]):Qn?(bo=Sn=x(co*x(3)),Qn=Mn,Tn=x(y(Rn))>2],go=u[Qn>>2],no=u[(Qn=Xn+(Tn+uo<<3)|0)+4>>2],oo=u[Qn>>2]):(v[16+qo>>3]=Wn,q[qo>>2]=a,v[8+qo>>3]=Yn,Y(4,1107,qo)),x(Un+Vn)<=x(1)?(u[On+po>>2]=x(go+x(x(oo-go)*Un))+x(x(ko-go)*Vn),Rn=x(fo+x(x(no-fo)*Un)),x(x(jo-fo)*Vn)):(Rn=x(x(1)-Un),Sn=x(x(1)-Vn),u[On+po>>2]=x(io+x(x(ko-io)*Rn))+x(x(oo-io)*Sn),Rn=x(ho+x(x(jo-ho)*Rn)),x(x(no-ho)*Sn)))):(Qn=x(y(bo=Sn))>2]=x(x(x(Rn*x(Sn*u[Tn>>2]))+x(Rn*x(Un*u[Tn+8>>2])))+x(Vn*x(Sn*u[Qn>>2])))+x(Vn*x(Un*u[Qn+8>>2])),Rn=x(x(x(Rn*x(Sn*u[Tn+4>>2]))+x(Rn*x(Un*u[Tn+12>>2])))+x(Vn*x(Sn*u[Qn+4>>2]))),x(Vn*x(Un*u[Qn+12>>2]))):x(Un+Vn)<=x(1)?(Rn=x(x(x(1)-Un)-Vn),Tn=Xn+(Qn<<3)|0,Qn=Xn+(Qn+mo<<3)|0,u[On+po>>2]=x(x(Rn*u[Tn>>2])+x(Un*u[Tn+8>>2]))+x(Vn*u[Qn>>2]),Rn=x(x(Rn*u[Tn+4>>2])+x(Un*u[Tn+12>>2])),x(Vn*u[Qn+4>>2])):(Rn=x(x(Un+x(-1))+Vn),Tn=Xn+(Qn+mo<<3)|0,Sn=x(x(1)-Un),Wn=x(x(1)-Vn),Qn=Xn+(Qn<<3)|0,u[On+po>>2]=x(x(Rn*u[Tn+8>>2])+x(Sn*u[Tn>>2]))+x(Wn*u[Qn+8>>2]),Rn=x(x(Rn*u[Tn+12>>2])+x(Sn*u[Tn+4>>2])),x(Wn*u[Qn+12>>2]))),u[4+(On+po|0)>>2]=Rn+Sn,(0|Pn)==(0|(a=a+1|0)))break;L=32+qo|0},n[2]=function(a,uf){a|=0,uf|=0;var vf=0,wf=0,xf=0,yf=0,zf=0,Af=x(0),Bf=0,Cf=0,Ef=(x(0),0),Ff=0,yf=q[a+324>>2],vf=q[a+320>>2],zf=q[a+312>>2];-1==(0|(wf=q[(xf=zf+(uf<<5)|0)+8>>2]))?(q[(Ff=vf)+(vf=uf<<2)>>2]=q[q[a+152>>2]+(q[xf+16>>2]<<2)>>2],q[vf+yf>>2]=1065353216):(Bf=q[xf+16>>2],Cf=q[q[a+156>>2]+(Bf<<2)>>2],n[q[24+(zf+(wf<<5)|0)>>2]](a,wf,Cf,Cf,q[16+(q[a- -64>>2]+w(Bf,24)|0)>>2]),Af=u[q[a+152>>2]+(q[xf+16>>2]<<2)>>2],xf=q[xf+8>>2]<<2,u[(wf=uf<<2)+vf>>2]=Af*u[xf+vf>>2],q[wf+yf>>2]=q[xf+yf>>2]),4<=r[q[q[a>>2]>>2]+4|0]&&(yf=uf<<2,vf=q[a+312>>2]+(uf<<5)|0,wf=q[vf+16>>2]<<2,xf=q[a+332>>2],uf=q[a+328>>2],-1==(0|(zf=q[vf+8>>2]))?(zf=q[a+160>>2],q[(vf=yf<<2)+uf>>2]=q[zf+(wf<<=2)>>2],q[(Bf=4|vf)+uf>>2]=q[(Cf=4|wf)+zf>>2],q[(Ef=8|vf)+uf>>2]=q[zf+(Ff=8|wf)>>2],q[uf+((yf|=3)<<2)>>2]=1065353216,a=q[a+164>>2],q[vf+xf>>2]=q[a+wf>>2],q[xf+Bf>>2]=q[a+Cf>>2],q[xf+Ef>>2]=q[a+Ff>>2]):(wf=(Cf=wf<<2)+q[a+160>>2]|0,u[(vf=(Bf=yf<<2)+uf|0)>>2]=u[wf>>2]*u[(zf=(Ef=zf<<4)+uf|0)>>2],u[vf+4>>2]=u[wf+4>>2]*u[zf+4>>2],u[vf+8>>2]=u[wf+8>>2]*u[zf+8>>2],q[uf+((yf|=3)<<2)>>2]=1065353216,a=Cf+q[a+164>>2]|0,Ff=u[a>>2],Af=u[(vf=xf+Ef|0)>>2],u[(uf=xf+Bf|0)>>2]=x(Ff+Af)-x(Ff*Af),Ff=u[a+4>>2],Af=u[vf+4>>2],u[uf+4>>2]=x(Ff+Af)-x(Ff*Af),Ff=u[a+8>>2],Af=u[vf+8>>2],u[uf+8>>2]=x(Ff+Af)-x(Ff*Af)),q[xf+(yf<<2)>>2]=1065353216)},n[3]=function(a,dm,lm,mm,nm){a|=0,dm|=0,lm|=0,mm|=0,nm|=0;var sm,tm,um,wm,xm,om=0,om=(x(0),x(0),x(0),x(0),x(0),x(0),x(0),x(0),(dm=q[16+(q[a+312>>2]+(dm<<5)|0)>>2])<<2),qm=function(a){var mk,pk,nk,lk=x(0),ok=0;L=nk=L-16|0,j(a);a:if((mk=2147483647&(ok=b[0]))>>>0<=1061752794)lk=x(1),mk>>>0<964689920||(lk=ca(+a));else if(mk>>>0<=1081824209)pk=+a,lk=1075235812<=mk>>>0?x(-ca(((0|ok)<0?3.141592653589793:-3.141592653589793)+pk)):ba((0|ok)<=-1?1.5707963267948966+pk:1.5707963267948966-pk);else if(mk>>>0<=1088565717)lk=1085271520<=mk>>>0?ca(+a+((0|ok)<0?6.283185307179586:-6.283185307179586)):ba((0|ok)<=-1?-4.71238898038469-+a:+a-4.71238898038469);else if(lk=x(a-a),!(2139095040<=mk>>>0))if((mk=3&Ca(a,8+nk|0))>>>0<=2){b:switch(mk-1|0){default:lk=ca(v[8+nk>>3]);break a;case 0:lk=ba(-v[8+nk>>3]);break a;case 1:break b}lk=x(-ca(v[8+nk>>3]))}else lk=ba(v[8+nk>>3]);return L=16+nk|0,a=lk}(pm=x(x(x(u[4+(q[a+172>>2]+w(dm,12)|0)>>2]+u[om+q[a+288>>2]>>2])*x(3.1415927410125732))/x(180))),rm=u[om+q[a+276>>2]>>2],vm=q[om+q[a+296>>2]>>2],pm=function(a){var Dj,ik,jk=0,kk=0;L=ik=L-16|0,j(a);a:if((Dj=2147483647&(kk=b[0]))>>>0<=1061752794)Dj>>>0<964689920||(a=ba(+a));else if(Dj>>>0<=1081824209)jk=+a,a=Dj>>>0<=1075235811?(0|kk)<=-1?x(-ca(jk+1.5707963267948966)):ca(jk+-1.5707963267948966):ba(-(((0|kk)<0?3.141592653589793:-3.141592653589793)+jk));else if(Dj>>>0<=1088565717)jk=+a,a=Dj>>>0<=1085271519?(0|kk)<=-1?ca(jk+4.71238898038469):x(-ca(jk+-4.71238898038469)):ba(((0|kk)<0?6.283185307179586:-6.283185307179586)+jk);else if(2139095040<=Dj>>>0)a=x(a-a);else if((Dj=3&Ca(a,8+ik|0))>>>0<=2){b:switch(Dj-1|0){default:a=ba(v[8+ik>>3]);break a;case 0:a=ca(v[8+ik>>3]);break a;case 1:break b}a=ba(-v[8+ik>>3])}else a=x(-ca(v[8+ik>>3]));return L=16+ik|0,a}(pm);if((dm=0)<(0|nm))for(qm=x(rm*qm),tm=x(vm?-1:1),wm=x(qm*tm),sm=q[om+q[a+292>>2]>>2]?x(-1):x(1),xm=x(x(rm*pm)*sm),qm=x(qm*sm),rm=x(x(rm*x(-pm))*tm),pm=u[om+q[a+284>>2]>>2],tm=u[om+q[a+280>>2]>>2];;)if(om=(a=dm<<3)+mm|0,sm=u[(a=a+lm|0)>>2],um=u[a+4>>2],u[om+4>>2]=pm+x(x(xm*sm)+x(wm*um)),u[om>>2]=tm+x(x(qm*sm)+x(rm*um)),(0|nm)==(0|(dm=dm+1|0)))break},n[4]=function(a,Qe){a|=0,Qe|=0;var af,bf,cf,df,ef,Re,Se=0,Te=0,Ue=0,Ve=x(0),We=0,Xe=0,Ye=x(0),Ze=0,_e=0,$e=0;if(x(0),x(0),x(0),x(0),L=Re=L+-64|0,Ze=q[a+324>>2],_e=q[a+320>>2],Te=q[a+312>>2],-1==(0|(We=q[(Ue=Te+(Qe<<5)|0)+8>>2])))Se=q[Ue+16>>2]<<2,q[(Te=Qe<<2)+_e>>2]=q[Se+q[a+272>>2]>>2],q[Te+Ze>>2]=q[Se+q[a+276>>2]>>2];else{Se=q[Ue+16>>2]<<2,$e=q[Se+q[a+280>>2]>>2],q[24+Re>>2]=$e,Se=q[Se+q[a+284>>2]>>2],q[28+Re>>2]=Se,q[16+Re>>2]=0,bf=1==q[(Xe=Te+(We<<5)|0)+12>>2]?x(-10):x(-.10000000149011612),u[20+Re>>2]=bf,q[60+Re>>2]=Se,q[56+Re>>2]=$e,n[q[Xe+24>>2]](a,We,56+Re|0,48+Re|0,1),Ve=x(1),Te=9;b:{for(;;){if(Se=Te,Ye=x(Ve*x(0)),u[32+Re>>2]=Ye+u[56+Re>>2],af=x(bf*Ve),u[36+Re>>2]=af+u[60+Re>>2],n[q[Xe+24>>2]](a,We,32+Re|0,40+Re|0,1),cf=x(u[44+Re>>2]-u[52+Re>>2]),u[44+Re>>2]=cf,df=x(u[40+Re>>2]-u[48+Re>>2]),u[40+Re>>2]=df,cf!=x(0)||df!=x(0)){Te=q[44+Re>>2],q[8+Re>>2]=q[40+Re>>2],q[12+Re>>2]=Te;break b}if(u[32+Re>>2]=u[56+Re>>2]-Ye,u[36+Re>>2]=u[60+Re>>2]-af,n[q[Xe+24>>2]](a,We,32+Re|0,40+Re|0,1),Ye=x(u[40+Re>>2]-u[48+Re>>2]),u[40+Re>>2]=Ye,af=x(u[44+Re>>2]-u[52+Re>>2]),(u[44+Re>>2]=af)!=x(0)||Ye!=x(0)){u[12+Re>>2]=-af,u[8+Re>>2]=-Ye;break b}if(Te=Se+-1|0,Ve=x(Ve*x(.10000000149011612)),!Se)break}Y(3,1311,0)}Ve=function(a,Qf){var Rf=x(0);if((Rf=x(Aa(u[a+4>>2],u[a>>2])-Aa(u[Qf+4>>2],u[Qf>>2])))x(3.1415927410125732))for(;;)if(!((Rf=x(Rf+x(-6.2831854820251465)))>x(3.1415927410125732)))break;return Rf}(16+Re|0,8+Re|0),n[q[Xe+24>>2]](a,q[Ue+8>>2],24+Re|0,24+Re|0,1),Te=q[Ue+16>>2]<<2,q[Te+q[a+280>>2]>>2]=q[24+Re>>2],q[Te+q[a+284>>2]>>2]=q[28+Re>>2],Se=Te+q[a+288>>2]|0,u[Se>>2]=u[Se>>2]+x(x(Ve*x(-180))/x(3.1415927410125732)),Ue=q[Ue+8>>2]<<2,u[(Se=Qe<<2)+_e>>2]=u[Te+q[a+272>>2]>>2]*u[Ue+_e>>2],Te=Te+q[a+276>>2]|0,Ve=x(u[Te>>2]*u[Ue+Ze>>2]),u[Se+Ze>>2]=Ve,u[Te>>2]=Ve}4<=r[q[q[a>>2]>>2]+4|0]&&(Se=Qe<<2,Ue=q[a+312>>2]+(Qe<<5)|0,We=q[Ue+16>>2]<<2,Te=q[a+332>>2],Qe=q[a+328>>2],-1==(0|(Xe=q[Ue+8>>2]))?(Xe=q[a+300>>2],q[(Ue=Se<<2)+Qe>>2]=q[Xe+(We<<=2)>>2],q[(Ze=4|Ue)+Qe>>2]=q[(_e=4|We)+Xe>>2],q[($e=8|Ue)+Qe>>2]=q[Xe+(ef=8|We)>>2],q[Qe+((Se|=3)<<2)>>2]=1065353216,a=q[a+304>>2],q[Te+Ue>>2]=q[a+We>>2],q[Te+Ze>>2]=q[a+_e>>2],q[Te+$e>>2]=q[a+ef>>2]):(We=(_e=We<<2)+q[a+300>>2]|0,u[(Ue=(Ze=Se<<2)+Qe|0)>>2]=u[We>>2]*u[(Xe=($e=Xe<<4)+Qe|0)>>2],u[Ue+4>>2]=u[We+4>>2]*u[Xe+4>>2],u[Ue+8>>2]=u[We+8>>2]*u[Xe+8>>2],q[Qe+((Se|=3)<<2)>>2]=1065353216,a=_e+q[a+304>>2]|0,Ve=u[a>>2],Ye=u[(Ue=Te+$e|0)>>2],u[(Qe=Te+Ze|0)>>2]=x(Ve+Ye)-x(Ve*Ye),Ve=u[a+4>>2],Ye=u[Ue+4>>2],u[Qe+4>>2]=x(Ve+Ye)-x(Ve*Ye),Ve=u[a+8>>2],Ye=u[Ue+8>>2],u[Qe+8>>2]=x(Ve+Ye)-x(Ve*Ye)),q[Te+(Se<<2)>>2]=1065353216),L=Re+64|0},n[5]=function(a,qk){return a|=0,qk|=0,x(0),x(0),0|((a=u[a>>2])<(qk=u[qk>>2])?-1:qk>2])))for(zi=q[a+12>>2],yi=q[a+20>>2];;)if(u[(vi=ui<<2)+zi>>2]=u[Xh+vi>>2]*u[vi+yi>>2],!((0|(ui=ui+1|0))<(0|xi)))break;if(!((0|(xi=q[a>>2]))<1))if(zi=q[a+4>>2],Zh)for(vi=Xh=0;;){if(q[Zh>>2]){if((0|(ui=q[(yi=Xh<<2)+q[a+16>>2]>>2]))<1)wi=x(0);else for(Ai=ui+vi|0,Bi=q[a+12>>2],wi=x(0),ui=vi;;)if(wi=x(wi+u[Bi+(ui<<2)>>2]),!((0|(ui=ui+1|0))<(0|Ai)))break;u[Yh+yi>>2]=wi}if(Zh=Zh+4|0,vi=q[zi+(Xh<<2)>>2]+vi|0,!((0|(Xh=Xh+1|0))<(0|xi)))break}else for(yi=q[a+16>>2],Xh=Zh=0;;){if((0|(ui=q[(vi=Zh<<2)+yi>>2]))<=0)wi=x(0);else for(Ai=Xh+ui|0,Bi=q[a+12>>2],wi=x(0),ui=Xh;;)if(wi=x(wi+u[Bi+(ui<<2)>>2]),!((0|(ui=ui+1|0))<(0|Ai)))break;if(u[Yh+vi>>2]=wi,Xh=q[vi+zi>>2]+Xh|0,!((0|(Zh=Zh+1|0))<(0|xi)))break}},n[7]=function(a,Xh,Yh,Zh){a|=0,Xh|=0,Yh|=0,Zh|=0;var _h=0,$h=x(0),pi=0,qi=0,ri=0,si=0,ti=0;if(1<=(0|(si=q[a+8>>2])))for(qi=q[a+12>>2],ri=q[a+20>>2];;)if(u[(pi=_h<<2)+qi>>2]=u[Xh+pi>>2]*u[pi+ri>>2],!((0|(_h=_h+1|0))<(0|si)))break;if(!((0|(_h=q[a>>2]))<1))if(si=q[a+4>>2],Zh)for(pi=Xh=0;;){if(q[Zh>>2]){if((0|(_h=q[(qi=Xh<<2)+q[a+16>>2]>>2]))<1)$h=x(0);else for(ri=_h+pi|0,ti=q[a+12>>2],$h=x(0),_h=pi;;)if($h=x($h+u[ti+(_h<<2)>>2]),!((0|(_h=_h+1|0))<(0|ri)))break;_h=Yh+qi|0,$h=x($h+x(.0010000000474974513)),qi=x(y($h))>2]=qi,_h=q[a>>2]}if(Zh=Zh+4|0,pi=q[si+(Xh<<2)>>2]+pi|0,!((0|(Xh=Xh+1|0))<(0|_h)))break}else for(qi=q[a+16>>2],Xh=Zh=0;;){if((0|(_h=q[(pi=Zh<<2)+qi>>2]))<=0)$h=x(0);else for(ri=Xh+_h|0,ti=q[a+12>>2],$h=x(0),_h=Xh;;)if($h=x($h+u[ti+(_h<<2)>>2]),!((0|(_h=_h+1|0))<(0|ri)))break;if(_h=Yh+pi|0,$h=x($h+x(.0010000000474974513)),ri=x(y($h))>2]=ri,Xh=q[pi+si>>2]+Xh|0,!((0|(Zh=Zh+1|0))>2]))break}},n[8]=function(a,Xh,Yh,Zh,_h,$h){a|=0,Xh|=0,Yh|=0,Zh|=0,_h|=0,$h|=0;var ni,oi,ai=0,bi=0,ci=0,di=0,ei=0,fi=0,gi=0,hi=0,ji=0,ki=0,li=x(0),mi=0,ii=q[a>>2];if(!((0|ii)<1))if(ni=_h<<2,oi=q[a+4>>2],$h)for(;;){if(q[$h>>2]&&(ci=q[(ai=di<<2)+q[a+16>>2]>>2],gi=q[Yh+ai>>2],bi=q[Zh+ai>>2],(ai=(0|(hi=w(bi,_h)))<1)||$(gi,0,w(bi,ni)),!(ai|(0|ci)<1)))for(ji=ci+fi|0,ki=q[a+20>>2],ai=fi;;){for(li=u[(bi=ai<<2)+ki>>2],mi=q[Xh+bi>>2],ei=0;;)if(u[(bi=(ci=ei<<2)+gi|0)>>2]=u[bi>>2]+x(li*u[ci+mi>>2]),(0|hi)==(0|(ei=ei+1|0)))break;if(!((0|(ai=ai+1|0))<(0|ji)))break}if($h=$h+4|0,fi=q[(di<<2)+oi>>2]+fi|0,!((0|(di=di+1|0))<(0|ii)))break}else for($h=0;;){if(ci=q[(di=$h<<2)+q[a+16>>2]>>2],gi=q[Yh+di>>2],bi=q[Zh+di>>2],(ai=(0|(hi=w(bi,_h)))<1)||$(gi,0,w(bi,ni)),!(ai|(0|ci)<=0))for(ji=ci+fi|0,ki=q[a+20>>2],ai=fi;;){for(li=u[(bi=ai<<2)+ki>>2],mi=q[Xh+bi>>2],ei=0;;)if(u[(bi=(ci=ei<<2)+gi|0)>>2]=u[bi>>2]+x(li*u[ci+mi>>2]),(0|hi)==(0|(ei=ei+1|0)))break;if(!((0|(ai=ai+1|0))<(0|ji)))break}if(fi=q[di+oi>>2]+fi|0,!((0|($h=$h+1|0))<(0|ii)))break}},n[9]=function(a){a|=0;var Hp,Ip,Jp,Dp=0,Ep=0,Fp=0,Gp=0;if(!(q[a+764>>2]||(0|(Dp=q[a+336>>2]))<1))for(Ip=(Ep=q[a+340>>2])+w(Dp,20)|0,Dp=q[a+428>>2],Gp=q[a+448>>2];;){if(q[Dp>>2]&&!((0|(Fp=q[Ep+16>>2]))<(a=1)))for(Fp<<=1,Jp=q[Gp>>2];;)if(u[(Hp=(a<<2)+Jp|0)>>2]=-u[Hp>>2],!((0|(a=a+2|0))<(0|Fp)))break;if(Gp=Gp+4|0,Dp=Dp+4|0,!((Ep=Ep+20|0)>>>0>>0))break}},n[10]=function(a,Cl,dm){var fm;return aa(fm=q[(a|=0)+20>>2],Cl|=0,Cl=(dm|=0)>>>0<(Cl=q[a+16>>2]-fm|0)>>>0?dm:Cl),q[a+20>>2]=Cl+q[a+20>>2],0|dm},n[11]=function(a,qk,Cl,Dl,El,Fl){a|=0,qk=+qk,Cl|=0,Dl|=0,El|=0,Fl|=0;var Sl,am,Kl,Gl=0,Hl=0,Il=0,Jl=0,Ll=0,Ml=0,Nl=0,Ol=0,Pl=0,Ql=0,Rl=0,Tl=0,Ul=0,Vl=0,Wl=0,Yl=0;if(q[44+(L=Kl=L-560|0)>>2]=0,h(+qk),Gl=0|b[1],am=4294967295>>0?0:1,Wl=(0|Gl)<-1||((0|Gl)<=-1?am:0)?(h(+(qk=-qk)),Gl=0|b[1],b[0],Vl=1,4208):2048&El?(Vl=1,4211):(Vl=1&El)?4214:4209,2146435072==(2146435072&Gl))_(a,32,Cl,Ml=Vl+3|0,-65537&El),Z(a,Wl,Vl),Dl=Fl>>>5&1,Z(a,qk!=qk?Dl?4235:4239:Dl?4227:4231,3);else if(qk=function Ia(a,Wb){var Yb,ic,Xb=0;if(h(+a),Xb=0|b[1],Yb=0|b[0],2047!=(0|(Xb=(ic=Xb)>>>20&2047))){if(!Xb)return Xb=Wb,Wb=0==a?0:(a=Ia(0x10000000000000000*a,Wb),q[Wb>>2]+-64|0),q[Xb>>2]=Wb,a;q[Wb>>2]=Xb+-1022,f(0,0|Yb),f(1,-2146435073&ic|1071644672),a=+g()}return a}(qk,44+Kl|0),0!=(qk+=qk)&&(q[44+Kl>>2]=q[44+Kl>>2]+-1),Sl=16+Kl|0,97==(0|(am=32|Fl))){if(Rl=(Ql=32&Fl)?Wl+9|0:Wl,!(11
>>0)&&(Gl=12-Dl|0)){for(Tl=8;;)if(Tl*=16,!(Gl=Gl+-1|0))break;qk=45==r[0|Rl]?-(Tl+(-qk-Tl)):qk+Tl-Tl}for((0|Sl)==(0|(Gl=ga((Il=(Gl=q[44+Kl>>2])>>31)^Gl+Il,0,Sl)))&&(o[15+Kl|0]=48,Gl=15+Kl|0),Ll=2|Vl,Il=q[44+Kl>>2],o[0|(Pl=Gl+-2|0)]=Fl+15,o[Gl+-1|0]=(0|Il)<0?45:43,Gl=8&El,Hl=16+Kl|0;;)if(Fl=Hl,Ol=Ql,Il=y(qk)<2147483648?~~qk:-2147483648,o[0|Hl]=Ol|r[Il+4192|0],1!=((Hl=Fl+1|0)-(16+Kl|0)|0)|(0==(qk=16*(qk-(0|Il)))?!(Gl|0<(0|Dl)):0)||(o[Fl+1|0]=46,Hl=Fl+2|0),0==qk)break;_(a,32,Cl,Ml=(Fl=!Dl|(0|Dl)<=((Hl-Kl|0)-18|0)?((Sl-(16+Kl|0)|0)-Pl|0)+Hl|0:2+((Dl+Sl|0)-Pl|0)|0)+Ll|0,El),Z(a,Rl,Ll),_(a,48,Cl,Ml,65536^El),Z(a,16+Kl|0,Dl=Hl-(16+Kl|0)|0),_(a,48,Fl-((Gl=Dl)+(Dl=Sl-Pl|0)|0)|0,0,0),Z(a,Pl,Dl)}else{for(Gl=(0|Dl)<0,0==qk?Jl=q[44+Kl>>2]:(Jl=q[44+Kl>>2]+-28|0,q[44+Kl>>2]=Jl,qk*=268435456),Nl=Gl?6:Dl,Il=Ql=(0|Jl)<0?48+Kl|0:336+Kl|0;;)if(Il=(Dl=Il)+4|0,0==(qk=1e9*(qk-((q[Dl>>2]=Gl=qk<4294967296&0<=qk?~~qk>>>0:0)>>>0))))break;if((0|Jl)<1)Gl=Il,Hl=Ql;else for(Hl=Ql;;){if(Pl=(0|Jl)<29?Jl:29,!((Gl=Il+-4|0)>>>0>>0)){for(Dl=Pl,Ol=0;;)if(Yl=Ol,Ol=q[(Rl=Gl)>>2],Ll=31&Dl,Ll=32<=(63&Dl)>>>(Ml=0)?(Jl=Ol<>>32-Ll,Ol<>>0>>0?Ml+1|0:Ml,Yl=Rl,Rl=sd(Ol=td(Ll=Ol,Ml,1e9),M,1e9),q[Yl>>2]=Ll-Rl,!(Hl>>>0<=(Gl=Gl+-4|0)>>>0))break;(Dl=Ol)&&(q[(Hl=Hl+-4|0)>>2]=Dl)}for(;;)if(!(Hl>>>0<(Gl=Il)>>>0)||q[(Il=Gl+-4|0)>>2])break;if(Jl=q[44+Kl>>2]-Pl|0,Il=Gl,!(0<(0|(q[44+Kl>>2]=Jl))))break}if((0|Jl)<=-1)for(Ul=1+((Nl+25|0)/9|0)|0,Pl=102==(0|am);;){if(Ol=(0|Jl)<-9?9:0-Jl|0,Gl>>>0<=Hl>>>0)Hl=q[Hl>>2]?Hl:Hl+4|0;else{for(Rl=1e9>>>Ol,Ll=-1<>2],q[Il>>2]=(Dl>>>Ol)+Jl,Jl=w(Rl,Dl&Ll),!((Il=Il+4|0)>>>0>>0))break;Hl=q[Hl>>2]?Hl:Hl+4|0,Jl&&(q[Gl>>2]=Jl,Gl=Gl+4|0)}if(Jl=Ol+q[44+Kl>>2]|0,Gl=(0|Ul)>2?Dl+(Ul<<2)|0:Gl,!((0|(q[44+Kl>>2]=Jl))<0))break}if(!(Gl>>>(Il=0)<=Hl>>>0||(Il=w(Ql-Hl>>2,9),(Dl=q[Hl>>2])>>>0<(Jl=10))))for(;;)if(Il=Il+1|0,!((Jl=w(Jl,10))>>>0<=Dl>>>0))break;if((0|(Dl=(Nl-(102==(0|am)?0:Il)|0)-(103==(0|am)&0!=(0|Nl))|0))<(w(Gl-Ql>>2,9)+-9|0)){if(Ml=(Ql+((Dl=(0|(Ll=Dl+9216|0))/9|0)<<2)|0)-4092|0,Jl=10,(0|(Dl=1+(Ll-w(Dl,9)|0)|0))<=8)for(;;)if(Jl=w(Jl,10),9==(0|(Dl=Dl+1|0)))break;if(Ul=Ml+4|0,((Pl=(Rl=q[Ml>>2])-w(Jl,Ll=(Rl>>>0)/(Jl>>>0)|0)|0)||(0|Ul)!=(0|Gl))&&(Tl=Pl>>>0<(Dl=Jl>>>1)>>>0?.5:(0|Gl)==(0|Ul)&&(0|Dl)==(0|Pl)?1:1.5,qk=1&Ll?9007199254740994:9007199254740992,!Vl|45!=r[0|Wl]||(Tl=-Tl,qk=-qk),q[Ml>>2]=Dl=Rl-Pl|0,qk+Tl!=qk)){if(1e9<=(q[Ml>>2]=Dl=Dl+Jl|0)>>>0)for(;;)if((Ml=Ml+-4|(q[Ml>>2]=0))>>>0>>0&&(q[(Hl=Hl+-4|0)>>2]=0),Dl=q[Ml>>2]+1|0,!(999999999<(q[Ml>>2]=Dl)>>>0))break;if(Il=w(Ql-Hl>>2,9),!((Dl=q[Hl>>2])>>>0<(Jl=10)))for(;;)if(Il=Il+1|0,!((Jl=w(Jl,10))>>>0<=Dl>>>0))break}Gl=(Dl=Ml+4|0)>>>0>>0?Dl:Gl}j:{for(;;){if((Pl=Gl)>>>(Rl=0)<=Hl>>>0)break j;if(q[(Gl=Pl+-4|0)>>2])break}Rl=1}if(103!=(0|am))Ll=8&El;else if(Nl=((Dl=(0|Il)<(0|(Gl=Nl||1))&-5<(0|Il))?-1^Il:-1)+Gl|0,Fl=(Dl?-1:-2)+Fl|0,!(Ll=8&El)){if(Gl=9,Rl&&(Ll=q[Pl+-4>>2])&&!((Ll>>>(Gl=0))%(Dl=10)))for(;;)if(Gl=Gl+1|0,(Ll>>>0)%((Dl=w(Dl,10))>>>0))break;Dl=w(Pl-Ql>>2,9)+-9|0,Nl=102==(32|Fl)?((Ll=0)|Nl)<(0|(Dl=0<(0|(Dl=Dl-Gl|0))?Dl:0))?Nl:Dl:((Ll=0)|Nl)<(0|(Dl=0<(0|(Dl=(Dl+Il|0)-Gl|0))?Dl:0))?Nl:Dl}if(Ml=0!=(0|(Jl=Nl|Ll)),Dl=a,Yl=Cl,Gl=0<(0|Il)?Il:0,102!=(0|(Ol=32|Fl))){if((Sl-(Gl=ga((Gl=Il>>31)+Il^Gl,0,Sl))|0)<=1)for(;;)if(o[0|(Gl=Gl+-1|0)]=48,!((Sl-Gl|0)<2))break;o[0|(Ul=Gl+-2|0)]=Fl,o[Gl+-1|0]=(0|Il)<0?45:43,Gl=Sl-Ul|0}if(_(Dl,32,Yl,Ml=1+(Gl+(Ml+(Nl+Vl|0)|0)|0)|0,El),Z(a,Wl,Vl),_(a,48,Cl,Ml,65536^El),102==(0|Ol)){for(Dl=16+Kl|8,Il=16+Kl|9,Hl=Fl=Ql>>>0>>0?Ql:Hl;;){if(Gl=ga(q[Hl>>2],0,Il),(0|Fl)!=(0|Hl)){if(!(Gl>>>0<=16+Kl>>>0))for(;;)if(o[0|(Gl=Gl+-1|0)]=48,!(16+Kl>>>0>>0))break}else(0|Gl)==(0|Il)&&(o[24+Kl|0]=48,Gl=Dl);if(Z(a,Gl,Il-Gl|0),!((Hl=Hl+4|0)>>>0<=Ql>>>0))break}Jl&&Z(a,4243,1);p:if(!((0|Nl)<1|Pl>>>0<=Hl>>>0))for(;;){if(16+Kl>>>0<(Gl=ga(q[Hl>>2],0,Il))>>>0)for(;;)if(o[0|(Gl=Gl+-1|0)]=48,!(16+Kl>>>0>>0))break;if(Z(a,Gl,(0|Nl)<9?Nl:9),Nl=Nl+-9|0,Pl>>>0<=(Hl=Hl+4|0)>>>0)break p;if(!(0<(0|Nl)))break}_(a,48,Nl+9|0,9,0)}else{q:if(!((0|Nl)<0))for(Fl=Rl?Pl:Hl+4|0,Dl=16+Kl|8,Ql=16+Kl|9,Il=Hl;;){if((0|Ql)==(0|(Gl=ga(q[Il>>2],0,Ql)))&&(o[24+Kl|0]=48,Gl=Dl),(0|Hl)!=(0|Il)){if(!(Gl>>>0<=16+Kl>>>0))for(;;)if(o[0|(Gl=Gl+-1|0)]=48,!(16+Kl>>>0>>0))break}else Z(a,Gl,1),Gl=Gl+1|0,(0|Nl)<1&&!Ll||Z(a,4243,1);if(Z(a,Ol=Gl,(0|(Gl=Ql-Gl|0))<(0|Nl)?Gl:Nl),Nl=Nl-Gl|0,Fl>>>0<=(Il=Il+4|0)>>>0)break q;if(!(-1<(0|Nl)))break}_(a,48,Nl+18|0,18,0),Z(a,Ul,Sl-Ul|0)}}return _(a,32,Cl,Ml,8192^El),L=560+Kl|0,0|((0|Ml)<(0|Cl)?Cl:Ml)},n[12]=function(a,qk){a|=0;var Lk=0,Lk=qk|=0;qk=q[qk>>2]+15&-16,q[Lk>>2]=qk+16,Lk=a,a=Cc(q[qk>>2],q[qk+4>>2],q[qk+8>>2],q[qk+12>>2]),v[Lk>>3]=a},n[13]=function(a){return 0},n[14]=function(a,qk,Lk){qk|=0,Lk|=0;var zl,vl,ul=0,wl=0,xl=0,yl=0;for(L=vl=L-32|0,ul=q[(a|=0)+28>>2],q[16+vl>>2]=ul,xl=q[a+20>>2],q[28+vl>>2]=Lk,q[24+vl>>2]=qk,xl=(q[20+vl>>2]=qk=xl-ul|0)+Lk|0,yl=2,qk=16+vl|0;;){a:{if((wl=(ul=0)|K(q[a+60>>2],0|qk,0|yl,12+vl|0))&&(q[2178]=wl,ul=-1),(0|(ul=ul?q[12+vl>>2]=-1:q[12+vl>>2]))==(0|xl))qk=q[a+44>>2],q[a+28>>2]=qk,q[a+20>>2]=qk,q[a+16>>2]=qk+q[a+48>>2],a=Lk;else{if(-1<(0|ul))break a;q[a+28>>2]=0,q[a+16>>2]=0,q[a+20>>2]=0,q[a>>2]=32|q[a>>2],2!=((a=0)|yl)&&(a=Lk-q[qk+4>>2]|0)}return L=32+vl|0,0|a}wl=q[qk+4>>2],q[(qk=(zl=wl>>>0