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) <noreply@anthropic.com>
This commit is contained in:
zyc 2026-05-12 11:14:10 +08:00
commit 72e7df09cd
425 changed files with 189912 additions and 0 deletions

66
.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,669 @@
# AI 驱动虚拟形象渲染方案 v5.1
## 跨产品形态统一架构调研
| 项目 | 内容 |
|---|---|
| 产品形态 | ① 罐子Unity 3D + RK + 全息光仓,洛天依形象在跑)<br>② 洛天依 APP独立 APPUnity<br>③ 小型硬件统一 APP多设备入口统一流量与用户<br>④ ESP32-S3 小硬件(序列帧形态)<br>⑤ 未来 Live2D 形态 / 虚拟明星 / 写实数字人 |
| 当前底座 | **火山 RTC AIGC 全平台已跑通**(手机 / 罐子 / ESP32-S3<br>整套 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 配置里启用 SubtitleConfigSubtitleMode 设为 0对齐音频时间戳
2. Unity 端打印 subtitle 消息 JSON验证时间戳精度决定后续嘴型驱动算法
3. Unity 端最小可用 demo罐子上洛天依 AI 对话时嘴型自动同步(取代当前手 K
详见第八章。**总工时 3-4 人天,本周可全部完成**。
---
## 二、修订后的数据流
```mermaid
sequenceDiagram
participant U as 用户
participant App as 端侧 App<br/>(Unity罐子/手机/RK/ESP32)
participant R as 本地渲染引擎<br/>(Unity / Cubism / LVGL)
participant RTC as 火山 RTC<br/>(全平台 SDK)
participant AIGC as 火山 AIGC 云<br/>(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 / DOFBloom 用低质量档
- 720p 是合理目标,强求 1080p 会掉帧
### 3.6 关于嘴型精度的讨论
> Air 提到:"洛天依现在嘴型只是张/合,不是真嘴型,对 Q 版卡通形象其实够用。但未来做虚拟明星、真人写实数字人时,真嘴型才比较重要。"
这个判断对:
| 形象类型 | 嘴型精度要求 | 实现路径 |
|---|---|---|
| Q 版卡通(洛天依) | 张/合 + 表情即可 | 字时间戳 → 1 个 BlendShape 开合度,足够 |
| 写实/半写实虚拟艺人 | 6 visemeA/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 clipAI 只是按语义触发——美术做一次,无限复用。
---
## 四、其他产品形态参考
> 这一章是知识储备。当前不需要立即用,但未来做新产品时直接拿来用,不用重新调研。
### 4.1 当前手机 APP 架构
**两个 APP 分立**
| APP | 形态 | 当前技术栈 | 维护人 |
|---|---|---|---|
| **洛天依 APP**(独立) | 单 IP 独立产品3D 形象 | **Unity**(与罐子同一份工程) | 彭铭聪 |
| **小型硬件统一 APP** | 多产品入口,承载 ESP32-S3 等小硬件 | 原生Flutter / 待定) | TBD |
**为什么分立**
- 洛天依是单一 IP 产品沉浸式体验Unity 重资产合理
- 小型硬件整合成一个 APP 是为了**统一流量入口和用户管理**,调性轻快,不适合塞 Unity 这种重容器
### 4.2 小型硬件统一 APP 的渲染方案
按硬件对应的形象类型APP 内嵌不同渲染层:
```
[小型硬件统一 APPFlutter 推荐)]
├─ 用户中心 / 流量入口 / 统一登录
├─ 设备管理 / 配网 / 固件升级
├─ 多产品入口(卡皮巴拉、桌面机器人...
└─ 形象渲染层(按设备角色卡热加载)
├─ 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 形态(未来)
如果未来某个产品定位需要 Live2D2D 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 出 VRMpixiv 标准)
- 跨产品形象互通
**三条集成路径**
| 路径 | 出活速度 | 性能 | 推荐场景 |
|---|---|---|---|
| **A. WebView + three.js + three-vrm** | 快1-2 周) | 手机 60fps | 默认起步,可嵌入小型硬件统一 APP |
| **B. Unity as a Library + UniVRM** | 慢4-6 周搭基建) | 最好 | 3D 是核心体验、独立 APP |
| **C. Filamentthermion** | 中 | 好 | 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 sheetemotion × 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 万日元/平台/RegioniOS / 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 启用 SubtitleConfigSubtitleMode=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 BlendTreehappy/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重点参考: <https://github.com/volcengine/rtc-aigc-demo>
- 火山 RTC AI 实时字幕文档: <https://www.volcengine.com/docs/6348/1337284>
- 火山 RTC 获取 AI 状态文档: <https://www.volcengine.com/docs/6348/1415216>
- 火山 RTC 情绪识别与生成: <https://www.volcengine.com/docs/6348/2139328>
- 火山 RTC Function Calling: <https://www.volcengine.com/docs/6348/1554654>
- 火山 RTC 嵌入式硬件集成: <https://www.volcengine.com/docs/6348/1438400>
**反例(已排除,仅作判断依据)**
- 火山 RTC AI 数字人形象文档: <https://www.volcengine.com/docs/6348/1848567> ← 这是云端渲染推视频流方案,**我们不用这个**,但通过它的文档反推出 SubtitleMode=0 是给端侧渲染用的
**储备方案参考**
- Cubism SDK 下载: <https://www.live2d.com/en/sdk/download/native/>
- Cubism License: <https://www.live2d.com/en/sdk/license/>
- three-vrmpixiv 官方): <https://github.com/pixiv/three-vrm>
- TalkingHeadVRM lipsync 参考): <https://github.com/met4citizen/TalkingHead>
- Open-LLM-VTuber参考架构: <https://github.com/Open-LLM-VTuber/Open-LLM-VTuber>
- Cocos Creator 4 文档: <https://docs.cocos.com/creator/manual/zh/>
---
## 附录 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 类应用的特殊分类,需单独审核签约

View File

@ -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/.');

View File

@ -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',
],
},
);

View File

@ -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"
}
}

View File

@ -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のデフォルト値を保持する定数<br>
* <br>
* https://docs.live2d.com/cubism-editor-manual/standard-parametor-list/
*/
export const CubismDefaultParameterId = Object.freeze<Record<string, string>>({
// パーツ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;
}

View File

@ -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;

View File

@ -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<string, number>): boolean {
// 存在しない要素にアクセスするとエラーになるためValueがnullの場合はnullを代入する
const map: Map<string, Value> = 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<Value>;
/**
* 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;
}

View File

@ -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<BreathParameterData>): void {
this._breathParameters = breathParameters;
}
/**
*
* @return
*/
public getParameters(): Array<BreathParameterData> {
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<BreathParameterData>; // 呼吸にひもづいているパラメータのリスト
_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;
}

View File

@ -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<CubismIdHandle>): void {
this._parameterIds = parameterIds;
}
/**
* IDのリストの取得
* @return IDのリスト
*/
public getParameterIds(): Array<CubismIdHandle> {
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<CubismIdHandle>();
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<CubismIdHandle>; // 操作対象のパラメータの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;
}

View File

@ -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<LookParameterData>): void {
this._lookParameters = lookParameters;
}
/**
*
* @return
*/
public getParameters(): Array<LookParameterData> {
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<LookParameterData>();
}
_lookParameters: Array<LookParameterData>; // ターゲット追従に紐づいているパラメータのリスト
}
/**
*
*/
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;
}

View File

@ -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
*/
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<PartData>();
this._partGroupCounts = new Array<number>();
}
_partGroups: Array<PartData>; // パーツグループ
_partGroupCounts: Array<number>; // それぞれのパーツグループの個数
_fadeTimeSeconds: number; // フェード時間[秒]
_lastModel: CubismModel; // 前回操作したモデル
}
/**
*
*/
export class PartData {
/**
*
*/
constructor(v?: PartData) {
this.parameterIndex = 0;
this.partIndex = 0;
this.link = new Array<PartData>();
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<PartData>();
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<PartData>; // 連動するパラメータ
}
// 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;
}

View File

@ -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;
}

View File

@ -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<string, number>): 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;
}

View File

@ -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;
}

View File

@ -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<CubismId>();
}
/**
*
*/
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 IDNULL
*/
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<CubismId>; // 登録されている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;
}

View File

@ -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<Record<string, number>>({
vertexOffset: 0, // メッシュ頂点のオフセット値
vertexStep: 2 // メッシュ頂点のステップ値
});
export function csmDelete<T>(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内のリソースを初期化してモデルを表示可能な状態にします<br>
* 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;
}

View File

@ -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;
}
/**
*
*
* @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;
}
/**
*
*
* @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値に該当する次方程式の解を求める
* 0.01.0
*
* a * x^3 + b * x^2 + c * x + d = 0
*
* @param a ->
* @param b ->
* @param c ->
* @param d ->
* @return 0.01.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;
}

View File

@ -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();
}
/**
*
*
* @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 164x4の行列
*/
public setMatrix(tr: Float32Array): void {
for (let i = 0; i < 16; ++i) {
this._tr[i] = tr[i];
}
}
/**
*
*
* @return 164x4の行列
*/
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;
}

View File

@ -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<string, number>): 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;
}

View File

@ -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として消してよい※未検証
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;
}

View File

@ -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;
}

View File

@ -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;
}
/**
*
* @return X軸位置
*/
public getScreenLeft(): number {
return this._screenLeft;
}
/**
*
* @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;
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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<CubismModelUserDataNode> {
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<CubismModelUserDataNode>();
this._artMeshUserDataNode = new Array<CubismModelUserDataNode>();
}
/**
*
*
*
*/
public release(): void {
for (let i = 0; i < this._userDataNodes.length; ++i) {
this._userDataNodes[i] = null;
}
this._userDataNodes = null;
}
private _userDataNodes: Array<CubismModelUserDataNode>; // ユーザーデータ構造体配列
private _artMeshUserDataNode: Array<CubismModelUserDataNode>; // 閲覧リストの保持
}
// 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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<string>();
}
/**
*
*/
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;
}
/**
*
*
*
*
*
* @param beforeCheckTimeSeconds []
* @param motionTimeSeconds []
*/
public getFiredEvent(
beforeCheckTimeSeconds: number,
motionTimeSeconds: number
): Array<string> {
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<string>;
// モーション再生開始コールバック関数
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;
}

View File

@ -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;
}

View File

@ -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<ExpressionParameterValue>,
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<ExpressionParameter>();
}
private _parameters: Array<ExpressionParameter>; // 表情のパラメータ情報リスト
}
/**
*
*/
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;
}

View File

@ -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<ExpressionParameterValue>();
this._fadeWeights = new Array<number>();
}
/**
*
*/
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 = <CubismExpressionMotion>(
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<ExpressionParameterValue>; ///< モデルに適用する各パラメータの値
private _fadeWeights: Array<number>; ///< 再生中の表情のウェイト
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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<CubismIdHandle>;
private _audioProvider: IParameterProvider | null;
/**
* Constructor
*
* @param lipSyncIds Array of lip sync parameter IDs
* @param audioProvider Audio parameter provider
*/
constructor(
lipSyncIds: Array<CubismIdHandle>,
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<CubismIdHandle>,
audioProvider: IParameterProvider | null,
executionOrder: number
);
constructor(
lipSyncIds: Array<CubismIdHandle>,
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;
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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<CubismMotionCurve>();
this.segments = new Array<CubismMotionSegment>();
this.points = new Array<CubismMotionPoint>();
this.events = new Array<CubismMotionEvent>();
}
duration: number; // モーションの長さ[秒]
loop: boolean; // ループするかどうか
curveCount: number; // カーブの個数
eventCount: number; // UserDataの個数
fps: number; // フレームレート
curves: Array<CubismMotionCurve>; // カーブのリスト
segments: Array<CubismMotionSegment>; // セグメントのリスト
points: Array<CubismMotionPoint>; // ポイントのリスト
events: Array<CubismMotionEvent>; // イベントのリスト
}
// 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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<CubismMotionQueueEntry>();
}
/**
*
*/
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<CubismMotionQueueEntry> {
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<string> = 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<CubismMotionQueueEntry>; // モーション
_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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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.<br>
* 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;
}

View File

@ -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.<br>
* 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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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<CubismPhysicsSubRig>();
this.inputs = new Array<CubismPhysicsInput>();
this.outputs = new Array<CubismPhysicsOutput>();
this.particles = new Array<CubismPhysicsParticle>();
this.gravity = new CubismVector2(0, 0);
this.wind = new CubismVector2(0, 0);
this.fps = 0.0;
}
subRigCount: number; // 物理演算の物理点の個数
settings: Array<CubismPhysicsSubRig>; // 物理演算の物理点の管理のリスト
inputs: Array<CubismPhysicsInput>; // 物理演算の入力のリスト
outputs: Array<CubismPhysicsOutput>; // 物理演算の出力のリスト
particles: Array<CubismPhysicsParticle>; // 物理演算の物理点のリスト
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;
}

View File

@ -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;
}

View File

@ -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<T_ClippingContext>,
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<T_ClippingContext>
) {
this._renderTextureCount = 0;
this._clippingMaskBufferSize = 256;
this._clippingContextListForMask = new Array<T_ClippingContext>();
this._clippingContextListForDraw = new Array<T_ClippingContext>();
this._clippingContextListForOffscreen = new Array<T_ClippingContext>();
this._tmpBoundsOnModel = new csmRect();
this._tmpMatrix = new CubismMatrix44();
this._tmpMatrixForMask = new CubismMatrix44();
this._tmpMatrixForDraw = new CubismMatrix44();
this._clearedMaskBufferFlags = new Array<boolean>();
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<boolean>(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<number>();
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<number>
): 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<number>
): 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());
}
/**
*
* 使
* 4RGBA各チャンネルに一つずつマスクを配置し56RGBAを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<T_ClippingContext> {
return this._clippingContextListForDraw;
}
public getClippingContextListForOffscreen(): Array<T_ClippingContext> {
return this._clippingContextListForOffscreen;
}
/**
*
* @return
*/
public getClippingMaskBufferSize(): number {
return this._clippingMaskBufferSize;
}
/**
*
* @return
*/
public getRenderTextureCount(): number {
return this._renderTextureCount;
}
/**
* RGBA
* @param channelNo RGBA0: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<boolean>; //マスクのクリアフラグの配列
protected _channelColors: Array<CubismTextureColor>;
protected _clippingContextListForMask: Array<T_ClippingContext>; // マスク用クリッピングコンテキストのリスト
protected _clippingContextListForDraw: Array<T_ClippingContext>; // 描画用クリッピングコンテキストのリスト
protected _clippingContextListForOffscreen: Array<T_ClippingContext>; // オフスクリーン用クリッピングコンテキストのリスト
protected _clippingMaskBufferSize: number; // クリッピングマスクのバッファサイズ(初期値:256
protected _renderTextureCount: number; // 生成するレンダーテクスチャの枚数
protected _tmpMatrix: CubismMatrix44; // マスク計算用の行列
protected _tmpMatrixForMask: CubismMatrix44; // マスク計算用の行列
protected _tmpMatrixForDraw: CubismMatrix44; // マスク計算用の行列
protected _tmpBoundsOnModel: csmRect; // マスク配置計算用の矩形
protected _clippingContexttConstructor: ClippingContextConstructor<T_ClippingContext>;
}

View File

@ -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<CubismRenderTargetContainer>();
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<CubismRenderTargetContainer>; // オフスクリーン描画用レンダーターゲットのリスト
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<CubismRenderTargetContainer>();
}
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ごとのマネージャー
}

View File

@ -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コンテキスト
}

View File

@ -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.01.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;
}
/**
* α
* truefalseをセットする
*/
public setIsPremultipliedAlpha(enable: boolean): void {
this._isPremultipliedAlpha = enable;
}
/**
* α
* @return true α
* false α
*/
public isPremultipliedAlpha(): boolean {
return this._isPremultipliedAlpha;
}
/**
*
* truefalseをセットする
*/
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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 truefalse
*/
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 truefalse
*/
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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<T>(
curArray: Array<T>,
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;
}
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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"
]
}

View File

@ -0,0 +1,269 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>Avatar H5 Renderer (PoC)</title>
<style>
html, body {
overflow: hidden;
margin: 0;
height: 100%;
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
background: #1a1a1f;
}
body {
background-image: url('/back_class_normal.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
html {
overscroll-behavior-x: none;
touch-action: none;
}
body {
display: flex;
flex-wrap: wrap;
}
body > canvas {
width: 100vw;
height: 100vh;
display: block;
}
/* 调试面板 */
#debug-panel {
position: fixed;
top: 0;
right: 0;
width: 320px;
max-height: 100vh;
overflow-y: auto;
padding: 16px;
box-sizing: border-box;
background: rgba(15, 15, 20, 0.85);
backdrop-filter: blur(10px);
color: #e5e5e5;
font-size: 13px;
line-height: 1.5;
border-left: 1px solid #333;
z-index: 999;
}
#debug-panel h3 {
margin: 0 0 8px;
font-size: 14px;
color: #80c0ff;
letter-spacing: 0.5px;
}
#debug-panel section {
margin-bottom: 14px;
padding-bottom: 12px;
border-bottom: 1px solid #2a2a2f;
}
#debug-panel section:last-child { border-bottom: none; }
#debug-panel .row {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
#debug-panel button {
padding: 6px 10px;
border: 1px solid #444;
background: #222;
color: #ddd;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.12s;
}
#debug-panel button:hover {
background: #2d4a6b;
border-color: #5588cc;
color: #fff;
}
#debug-panel button.primary {
background: #2766b6;
border-color: #4488dd;
color: #fff;
}
#debug-panel button.primary:hover {
background: #3a85d6;
}
#debug-panel input[type=range] {
width: 100%;
margin-top: 6px;
}
#debug-panel .value {
color: #80c0ff;
font-family: ui-monospace, Menlo, monospace;
}
.hint {
font-size: 11px;
color: #888;
margin-top: 6px;
}
code {
color: #80c0ff;
font-family: ui-monospace, Menlo, monospace;
}
</style>
<!-- Live2D Cubism Core -->
<script src="/Core/live2dcubismcore.js"></script>
<!-- App entry -->
<script src="./src/main.ts" type="module"></script>
</head>
<body>
<div id="debug-panel">
<section>
<h3>角色状态</h3>
<div class="row">
<button data-act="state" data-arg="idle">idle 待机</button>
<button data-act="state" data-arg="listening">listening 倾听</button>
<button data-act="state" data-arg="thinking">thinking 思考</button>
<button data-act="state" data-arg="speaking">speaking 说话</button>
</div>
</section>
<section>
<h3>表情切换</h3>
<div class="row" id="expression-buttons">
<span class="hint">加载中...</span>
</div>
</section>
<section>
<h3>动作播放</h3>
<div class="row" id="motion-buttons">
<span class="hint">加载中...</span>
</div>
</section>
<section>
<h3>嘴型驱动 <span class="value" id="mouth-value">0.00</span></h3>
<input type="range" id="mouth-slider" min="0" max="100" value="0">
<div class="row" style="margin-top:6px">
<button data-act="mouth" data-arg="0"></button>
<button data-act="mouth" data-arg="0.5">半开</button>
<button data-act="mouth" data-arg="1">全开</button>
</div>
</section>
<section>
<h3>语义动作</h3>
<div class="row" id="action-buttons"></div>
<div class="hint">对应未来 LLM Function Call 触发</div>
</section>
<section>
<h3>跳舞</h3>
<div class="row">
<button class="primary" data-act="dance-start">💃 开始跳舞</button>
<button data-act="dance-stop">停止</button>
</div>
<div class="hint">程序化驱动身体参数 + 循环切 Dance motion</div>
</section>
<section>
<h3>事件模拟</h3>
<div class="row">
<button class="primary" data-act="mock-conversation">▶ 播放模拟对话</button>
</div>
<div class="hint">listening → thinking → 微笑+说话 → idle</div>
</section>
<section>
<h3>Console API</h3>
<div class="hint">
浏览器控制台可直接调:<br>
<code>avatar.setExpression("Smile")</code><br>
<code>avatar.playMotion("Idle", 0)</code><br>
<code>avatar.setMouthOpen(0.8)</code><br>
<code>avatar.listExpressions()</code><br>
<code>avatar.listMotions()</code>
</div>
</section>
</div>
<script type="module">
function waitReady() {
return new Promise((resolve) => {
const check = () => {
if (window.avatar) {
const exps = window.avatar.listExpressions();
const mots = window.avatar.listMotions();
if (exps.length > 0 || Object.keys(mots).length > 0) {
resolve();
return;
}
}
setTimeout(check, 150);
};
check();
});
}
waitReady().then(() => {
const expBox = document.getElementById('expression-buttons');
expBox.innerHTML = '';
window.avatar.listExpressions().forEach((name) => {
const btn = document.createElement('button');
btn.textContent = name;
btn.onclick = () => window.avatar.setExpression(name);
expBox.appendChild(btn);
});
const motionBox = document.getElementById('motion-buttons');
motionBox.innerHTML = '';
const motions = window.avatar.listMotions();
Object.entries(motions).forEach(([group, count]) => {
for (let i = 0; i < count; i++) {
const btn = document.createElement('button');
btn.textContent = `${group}#${i}`;
btn.onclick = () => window.avatar.playMotion(group, i);
motionBox.appendChild(btn);
}
});
const actionBox = document.getElementById('action-buttons');
actionBox.innerHTML = '';
window.avatar.listActions().forEach((name) => {
const btn = document.createElement('button');
btn.textContent = name;
btn.onclick = () => window.avatar.playAction(name);
actionBox.appendChild(btn);
});
console.log('[debug-panel] ready. avatar API:', window.avatar);
});
const slider = document.getElementById('mouth-slider');
const mouthVal = document.getElementById('mouth-value');
slider.addEventListener('input', () => {
const v = slider.value / 100;
mouthVal.textContent = v.toFixed(2);
window.avatar?.setMouthOpen(v);
});
document.querySelectorAll('[data-act]').forEach((btn) => {
btn.addEventListener('click', () => {
const act = btn.dataset.act;
const arg = btn.dataset.arg;
if (act === 'state') window.avatar?.setState(arg);
else if (act === 'mouth') {
const v = parseFloat(arg);
window.avatar?.setMouthOpen(v);
slider.value = v * 100;
mouthVal.textContent = v.toFixed(2);
} else if (act === 'mock-conversation') {
window.avatar?.playMockConversation();
} else if (act === 'dance-start') {
window.avatar?.startDance();
} else if (act === 'dance-stop') {
window.avatar?.stopDance();
}
});
});
</script>
</body>
</html>

3868
avatar-h5-renderer/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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": "*"
}
}

View File

@ -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`.

View File

@ -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)

View File

@ -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版です。
このファイルを本番環境で使用します。

View File

@ -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.

View File

@ -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

View File

@ -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<string>;
/** 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<Float32Array>;
/**
* Initializes instance.
*
* @param modelPtr Native model.
*/
constructor(modelPtr: number);
}
/** Cubism model parts */
class Parts {
/** Part count. */
count: number;
/** Part IDs. */
ids: Array<string>;
/** 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<string>;
/** 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<Int32Array>;
/** Number of vertices of each drawable. */
vertexCounts: Int32Array;
/** 2D vertex position data of each drawable. */
vertexPositions: Array<Float32Array>;
/** 2D texture coordinate data of each drawables. */
vertexUvs: Array<Float32Array>;
/** Number of triangle indices for each drawable. */
indexCounts: Int32Array;
/** Triangle index data for each drawable. */
indices: Array<Uint16Array>;
/** 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<Int32Array>;
/** 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. */
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,54 @@
/**
* 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.
*/
vec4 OverlapRgba(vec3 color, vec3 colorSource, vec3 colorDestination, vec3 parameter)
{
vec3 rgb = color * parameter.x + colorSource * parameter.y + colorDestination * parameter.z;
float alpha = parameter.x + parameter.y + parameter.z;
return vec4(rgb, alpha);
}
#if defined(ALPHA_BLEND_OVER)
vec4 AlphaBlend(vec3 color, vec4 colorSource, vec4 colorDestination)
{
vec3 parameter = vec3(colorSource.a * colorDestination.a, colorSource.a * (1.0 - colorDestination.a), colorDestination.a * (1.0 - colorSource.a));
return OverlapRgba(color, colorSource.rgb, colorDestination.rgb, parameter);
}
#elif defined(ALPHA_BLEND_ATOP)
vec4 AlphaBlend(vec3 color, vec4 colorSource, vec4 colorDestination)
{
vec3 parameter = vec3(colorSource.a * colorDestination.a, 0, colorDestination.a * (1.0 - colorSource.a));
return OverlapRgba(color, colorSource.rgb, colorDestination.rgb, parameter);
}
#elif defined(ALPHA_BLEND_OUT)
vec4 AlphaBlend(vec3 color, vec4 colorSource, vec4 colorDestination)
{
vec3 parameter = vec3(0.0, 0.0, colorDestination.a * (1.0 - colorSource.a));
return OverlapRgba(color, colorSource.rgb, colorDestination.rgb, parameter);
}
#elif defined(ALPHA_BLEND_CONJOINTOVER)
vec4 AlphaBlend(vec3 color, vec4 colorSource, vec4 colorDestination)
{
vec3 parameter = vec3(min(colorSource.a, colorDestination.a), max(colorSource.a - colorDestination.a, 0.0), max(colorDestination.a - colorSource.a, 0.0));
return OverlapRgba(color, colorSource.rgb, colorDestination.rgb, parameter);
}
#elif defined(ALPHA_BLEND_DISJOINTOVER)
vec4 AlphaBlend(vec3 color, vec4 colorSource, vec4 colorDestination)
{
vec3 parameter = vec3(max(colorSource.a + colorDestination.a - 1.0, 0.0), min(colorSource.a, 1.0 - colorDestination.a), min(colorDestination.a, 1.0 - colorSource.a));
return OverlapRgba(color, colorSource.rgb, colorDestination.rgb, parameter);
}
#else
#error not supported alpha blend function
#endif

View File

@ -0,0 +1,298 @@
/**
* 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.
*/
#if defined(COLOR_BLEND_NORMAL)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return colorSource;
}
#elif defined(COLOR_BLEND_ADDCOMPATIBLE)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return vec3(0.0);
}
#elif defined(COLOR_BLEND_MULTCOMPATIBLE)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return vec3(0.0);
}
#elif defined(COLOR_BLEND_ADD)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return min(colorSource + colorDestination, 1.0);
}
#elif defined(COLOR_BLEND_ADDGLOW)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return colorSource + colorDestination;
}
#elif defined(COLOR_BLEND_DARKEN)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return min(colorSource, colorDestination);
}
#elif defined(COLOR_BLEND_MULTIPLY)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return colorSource * colorDestination;
}
#elif defined(COLOR_BLEND_COLORBURN)
float ColorBurn(float colorSource, float colorDestination)
{
if (abs(colorDestination - 1.0) < 0.000001)
{
return 1.0;
}
else if (abs(colorSource) < 0.000001)
{
return 0.0;
}
return 1.0 - min(1.0, (1.0 - colorDestination) / colorSource);
}
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return vec3(
ColorBurn(colorSource.r, colorDestination.r),
ColorBurn(colorSource.g, colorDestination.g),
ColorBurn(colorSource.b, colorDestination.b)
);
}
#elif defined(COLOR_BLEND_LINEARBURN)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return max(vec3(0.0), colorSource + colorDestination - 1.0);
}
#elif defined(COLOR_BLEND_LIGHTEN)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return max(colorSource, colorDestination);
}
#elif defined(COLOR_BLEND_SCREEN)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return colorSource + colorDestination - colorSource * colorDestination;
}
#elif defined(COLOR_BLEND_COLORDODGE)
float ColorDodge(float colorSource, float colorDestination)
{
if (colorDestination <= 0.0)
{
return 0.0;
}
else if (colorSource == 1.0)
{
return 1.0;
}
return min(1.0, colorDestination / (1.0 - colorSource));
}
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return vec3(
ColorDodge(colorSource.r, colorDestination.r),
ColorDodge(colorSource.g, colorDestination.g),
ColorDodge(colorSource.b, colorDestination.b)
);
}
#elif defined(COLOR_BLEND_OVERLAY)
float Overlay(float colorSource, float colorDestination)
{
float mul = 2.0 * colorSource * colorDestination;
float scr = 1.0 - 2.0 * (1.0 - colorSource) * (1.0 - colorDestination) ;
return colorDestination < 0.5 ? mul : scr ;
}
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return vec3(
Overlay(colorSource.r, colorDestination.r),
Overlay(colorSource.g, colorDestination.g),
Overlay(colorSource.b, colorDestination.b)
);
}
#elif defined(COLOR_BLEND_SOFTLIGHT)
float SoftLight(float colorSource, float colorDestination)
{
float val1 = colorDestination - (1.0 - 2.0 * colorSource) * colorDestination * (1.0 - colorDestination);
float val2 = colorDestination + (2.0 * colorSource - 1.0) * colorDestination * ((16.0 * colorDestination - 12.0) * colorDestination + 3.0);
float val3 = colorDestination + (2.0 * colorSource - 1.0) * (sqrt(colorDestination) - colorDestination);
if (colorSource <= 0.5)
{
return val1;
}
else if (colorDestination <= 0.25)
{
return val2;
}
return val3;
}
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return vec3(
SoftLight(colorSource.r, colorDestination.r),
SoftLight(colorSource.g, colorDestination.g),
SoftLight(colorSource.b, colorDestination.b)
);
}
#elif defined(COLOR_BLEND_HARDLIGHT)
float HardLight(float colorSource, float colorDestination)
{
float mul = 2.0 * colorSource * colorDestination;
float scr = 1.0 - 2.0 * (1.0 - colorSource) * (1.0 - colorDestination);
if (colorSource < 0.5)
{
return mul;
}
return scr;
}
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return vec3(
HardLight(colorSource.r, colorDestination.r),
HardLight(colorSource.g, colorDestination.g),
HardLight(colorSource.b, colorDestination.b)
);
}
#elif defined(COLOR_BLEND_LINEARLIGHT)
float LinearLight(float colorSource, float colorDestination)
{
float burn = max(0.0, 2.0 * colorSource + colorDestination - 1.0);
float dodge = min(1.0, 2.0 * (colorSource - 0.5) + colorDestination);
if (colorSource < 0.5)
{
return burn;
}
return dodge;
}
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return vec3(
LinearLight(colorSource.r, colorDestination.r),
LinearLight(colorSource.g, colorDestination.g),
LinearLight(colorSource.b, colorDestination.b)
);
}
#elif defined(COLOR_BLEND_HUE) || defined(COLOR_BLEND_COLOR)
const float rCoeff = 0.30;
const float gCoeff = 0.59;
const float bCoeff = 0.11;
float GetMax(vec3 rgbC)
{
return max(rgbC.r, max(rgbC.g, rgbC.b));
}
float GetMin(vec3 rgbC)
{
return min(rgbC.r, min(rgbC.g, rgbC.b));
}
float GetRange(vec3 rgbC)
{
return max(rgbC.r, max(rgbC.g, rgbC.b)) - min(rgbC.r, min(rgbC.g, rgbC.b));
}
float Saturation(vec3 rgbC)
{
return GetRange(rgbC);
}
float Luma(vec3 rgbC)
{
return rCoeff * rgbC.r + gCoeff * rgbC.g + bCoeff * rgbC.b;
}
vec3 ClipColor(vec3 rgbC)
{
float luma = Luma(rgbC);
float maxv = GetMax(rgbC);
float minv = GetMin(rgbC);
vec3 outputColor = rgbC;
outputColor = minv < 0.0 ? luma + (outputColor - luma) * luma / (luma - minv) : outputColor;
outputColor = maxv > 1.0 ? luma + (outputColor - luma) * (1.0 - luma) / (maxv - luma) : outputColor;
return outputColor;
}
vec3 SetLuma(vec3 rgbC, float luma)
{
return ClipColor(rgbC + (luma - Luma(rgbC)));
}
vec3 SetSaturation(vec3 rgbC, float saturation)
{
float maxv = GetMax(rgbC);
float minv = GetMin(rgbC);
float medv = rgbC.r + rgbC.g + rgbC.b - maxv - minv;
float outputMax, outputMed, outputMin;
outputMax = minv < maxv ? saturation : 0.0;
outputMed = minv < maxv ? (medv - minv) * saturation / (maxv - minv) : 0.0;
outputMin = 0.0;
if(rgbC.r == maxv)
{
return rgbC.b < rgbC.g ? vec3(outputMax, outputMed, outputMin) : vec3(outputMax, outputMin, outputMed);
}
else if(rgbC.g == maxv)
{
return rgbC.r < rgbC.b ? vec3(outputMin, outputMax, outputMed) : vec3(outputMed, outputMax, outputMin);
}
else // if(rgbC.b == maxv)
{
return rgbC.g < rgbC.r ? vec3(outputMed, outputMin, outputMax) : vec3(outputMin, outputMed, outputMax);
}
}
#if defined(COLOR_BLEND_HUE)
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return SetLuma(SetSaturation(colorSource, Saturation(colorDestination)), Luma(colorDestination));
}
#else
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination)
{
return SetLuma(colorSource, Luma(colorDestination)) ;
}
#endif
#else
#error not supported color blend function.
#endif

View File

@ -0,0 +1,18 @@
/**
* 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.
*/
precision mediump float;
varying vec2 v_texCoord;
uniform vec4 u_baseColor;
uniform sampler2D s_texture0;
void main()
{
gl_FragColor = texture2D(s_texture0, v_texCoord) * u_baseColor;
}

View File

@ -0,0 +1,29 @@
/**
* 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.
*/
precision mediump float;
varying vec2 v_texCoord; //v2f.texcoord
varying vec4 v_clipPos;
uniform sampler2D s_texture0; //_MainTex
uniform sampler2D s_texture1; // _ClippingMaskTex
uniform vec4 u_channelFlag;
uniform vec4 u_baseColor; //v2f.color
uniform vec4 u_multiplyColor;
uniform vec4 u_screenColor;
void main()
{
vec4 texColor = texture2D(s_texture0, v_texCoord);
texColor.rgb = texColor.rgb * u_multiplyColor.rgb;
texColor.rgb = (texColor.rgb + u_screenColor.rgb * texColor.a) - (texColor.rgb * u_screenColor.rgb);
vec4 col_formask = texColor * u_baseColor;
vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag;
float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a;
col_formask = col_formask * (1.0 - maskVal);
gl_FragColor = col_formask;
}

View File

@ -0,0 +1,29 @@
/**
* 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.
*/
precision mediump float;
varying vec2 v_texCoord; //v2f.texcoord
varying vec4 v_clipPos;
uniform vec4 u_baseColor; //v2f.color
uniform vec4 u_channelFlag;
uniform sampler2D s_texture0; //_MainTex
uniform sampler2D s_texture1; // _ClippingMaskTex
uniform vec4 u_multiplyColor;
uniform vec4 u_screenColor;
void main()
{
vec4 texColor = texture2D(s_texture0, v_texCoord);
texColor.rgb = texColor.rgb * u_multiplyColor.rgb;
texColor.rgb = (texColor.rgb + u_screenColor.rgb * texColor.a) - (texColor.rgb * u_screenColor.rgb);
vec4 col_formask = texColor * u_baseColor;
vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag;
float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a;
col_formask = col_formask * maskVal;
gl_FragColor = col_formask;
}

View File

@ -0,0 +1,23 @@
/**
* 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.
*/
precision mediump float;
varying vec2 v_texCoord; //v2f.texcoord
uniform vec4 u_baseColor; //v2f.color
uniform sampler2D s_texture0; //_MainTex
uniform vec4 u_multiplyColor;
uniform vec4 u_screenColor;
void main()
{
vec4 texColor = texture2D(s_texture0, v_texCoord);
texColor.rgb = texColor.rgb * u_multiplyColor.rgb;
texColor.rgb = (texColor.rgb + u_screenColor.rgb * texColor.a) - (texColor.rgb * u_screenColor.rgb);
vec4 color = texColor * u_baseColor;
gl_FragColor = vec4(color.rgb, color.a);
}

View File

@ -0,0 +1,67 @@
/**
* 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.
*/
varying vec2 v_texCoord; //v2f.texcoord
varying vec2 v_blendCoord;
varying vec4 v_clipPos;
uniform sampler2D s_texture0; //_MainTex
uniform sampler2D s_blendTexture;
uniform vec4 u_baseColor; //v2f.color
uniform vec4 u_multiplyColor;
uniform vec4 u_screenColor;
uniform sampler2D s_texture1; // _ClippingMaskTex
uniform float u_invertClippingMask;
uniform vec4 u_channelFlag;
vec3 ColorBlend(vec3 colorSource, vec3 colorDestination);
vec4 AlphaBlend(vec3 C, vec3 Cs, float As, vec3 Cd, float Ad);
void main()
{
vec4 renderTextureColor = texture2D(s_blendTexture, v_blendCoord);
vec3 colorDestination = renderTextureColor.rgb;
float alphaDestination = renderTextureColor.a;
if (alphaDestination < 0.00001)
{
colorDestination = vec3(0.0, 0.0, 0.0);
}
else {
colorDestination /= alphaDestination;
}
vec4 texColor = texture2D(s_texture0, v_texCoord);
texColor.rgb *= u_multiplyColor.rgb;
texColor.rgb = (texColor.rgb + u_screenColor.rgb) - (texColor.rgb * u_screenColor.rgb);
texColor *= u_baseColor;
vec3 colorSource = texColor.rgb;
float alphaSource = texColor.a;
if (alphaSource < 0.00001)
{
colorSource = vec3(0.0, 0.0, 0.0);
}
else {
colorSource /= alphaSource;
}
#ifdef CLIPPING_MASK
float maskVal = 1.0;
vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag;
maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a;
maskVal = abs(u_invertClippingMask - maskVal);
alphaSource *= maskVal;
#endif
vec4 source = vec4(colorSource.r, colorSource.g, colorSource.b, alphaSource);
vec4 destination = vec4(colorDestination.r, colorDestination.g, colorDestination.b, alphaDestination);
gl_FragColor = AlphaBlend(ColorBlend(colorSource, colorDestination), source, destination);
}

View File

@ -0,0 +1,25 @@
/**
* 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.
*/
precision mediump float;
varying vec2 v_texCoord; //v2f.texcoord
varying vec4 v_myPos;
uniform vec4 u_baseColor; //v2f.color
uniform vec4 u_channelFlag;
uniform sampler2D s_texture0; //_MainTex
void main()
{
float isInside =
step(u_baseColor.x, v_myPos.x/v_myPos.w)
* step(u_baseColor.y, v_myPos.y/v_myPos.w)
* step(v_myPos.x/v_myPos.w, u_baseColor.z)
* step(v_myPos.y/v_myPos.w, u_baseColor.w);
gl_FragColor = u_channelFlag * texture2D(s_texture0, v_texCoord).a * isInside;
}

View File

@ -0,0 +1,19 @@
/**
* 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.
*/
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
uniform mat4 u_matrix;
void main()
{
gl_Position = u_matrix * a_position;
v_texCoord = a_texCoord;
v_texCoord.y = 1.0 - v_texCoord.y;
}

View File

@ -0,0 +1,31 @@
/**
* 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.
*/
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
varying vec2 v_blendCoord;
varying vec4 v_clipPos;
uniform mat4 u_matrix;
uniform mat4 u_clipMatrix;
void main()
{
gl_Position = u_matrix * a_position;
#ifdef CLIPPING_MASK
v_clipPos = u_clipMatrix * a_position;
#else
v_clipPos = vec4(0.0);
#endif
v_texCoord = a_texCoord;
v_texCoord.y = 1.0 - v_texCoord.y;
vec2 ndcPos = gl_Position.xy / gl_Position.w;
v_blendCoord = ndcPos * 0.5 + 0.5;
}

View File

@ -0,0 +1,17 @@
/**
* 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.
*/
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main()
{
v_texCoord = a_texCoord;
gl_Position = a_position;
}

View File

@ -0,0 +1,22 @@
/**
* 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.
*/
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
varying vec4 v_clipPos;
uniform mat4 u_matrix;
uniform mat4 u_clipMatrix;
void main()
{
gl_Position = u_matrix * a_position;
v_clipPos = u_clipMatrix * a_position;
v_texCoord = a_texCoord;
v_texCoord.y = 1.0 - v_texCoord.y;
}

View File

@ -0,0 +1,21 @@
/**
* 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.
*/
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
varying vec4 v_myPos;
uniform mat4 u_clipMatrix;
void main()
{
gl_Position = u_clipMatrix * a_position;
v_myPos = u_clipMatrix * a_position;
v_texCoord = a_texCoord;
v_texCoord.y = 1.0 - v_texCoord.y;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,334 @@
{
"Version": 3,
"Parameters": [
{
"Id": "ParamAngleX",
"GroupId": "ParamGroupFace",
"Name": "角度 X"
},
{
"Id": "ParamAngleY",
"GroupId": "ParamGroupFace",
"Name": "角度 Y"
},
{
"Id": "ParamAngleZ",
"GroupId": "ParamGroupFace",
"Name": "角度 Z"
},
{
"Id": "ParamTere",
"GroupId": "ParamGroupFace",
"Name": "照れ"
},
{
"Id": "ParamFaceForm",
"GroupId": "ParamGroupFace",
"Name": "顔の拡縮"
},
{
"Id": "ParamEyeLOpen",
"GroupId": "ParamGroupEyes",
"Name": "左目 開閉"
},
{
"Id": "ParamEyeLSmile",
"GroupId": "ParamGroupEyes",
"Name": "左目 笑顔"
},
{
"Id": "ParamEyeROpen",
"GroupId": "ParamGroupEyes",
"Name": "右目 開閉"
},
{
"Id": "ParamEyeRSmile",
"GroupId": "ParamGroupEyes",
"Name": "右目 笑顔"
},
{
"Id": "ParamEyeForm",
"GroupId": "ParamGroupEyes",
"Name": "眼 変形"
},
{
"Id": "ParamEyeBallForm",
"GroupId": "ParamGroupEyes",
"Name": "目玉 収縮"
},
{
"Id": "ParamTear",
"GroupId": "ParamGroupEyes",
"Name": "涙"
},
{
"Id": "ParamEyeBallX",
"GroupId": "ParamGroupEyes",
"Name": "目玉 X"
},
{
"Id": "ParamEyeBallY",
"GroupId": "ParamGroupEyes",
"Name": "目玉 Y"
},
{
"Id": "ParamBrowLY",
"GroupId": "ParamGroup",
"Name": "左眉 上下"
},
{
"Id": "ParamBrowRY",
"GroupId": "ParamGroup",
"Name": "右眉 上下"
},
{
"Id": "ParamBrowLX",
"GroupId": "ParamGroup",
"Name": "左眉 左右"
},
{
"Id": "ParamBrowRX",
"GroupId": "ParamGroup",
"Name": "右眉 左右"
},
{
"Id": "ParamBrowLAngle",
"GroupId": "ParamGroup",
"Name": "左眉 角度"
},
{
"Id": "ParamBrowRAngle",
"GroupId": "ParamGroup",
"Name": "右眉 角度"
},
{
"Id": "ParamBrowLForm",
"GroupId": "ParamGroup",
"Name": "左眉 変形"
},
{
"Id": "ParamBrowRForm",
"GroupId": "ParamGroup",
"Name": "右眉 変形"
},
{
"Id": "ParamMouthForm",
"GroupId": "ParamGroupMouth",
"Name": "口 変形"
},
{
"Id": "ParamMouthOpenY",
"GroupId": "ParamGroupMouth",
"Name": "口 開閉"
},
{
"Id": "ParamScarf",
"GroupId": "ParamGroup2",
"Name": "スカーフ揺れ"
},
{
"Id": "ParamBodyAngleX",
"GroupId": "ParamGroup2",
"Name": "体の回転 X"
},
{
"Id": "ParamBodyAngleY",
"GroupId": "ParamGroup2",
"Name": "体の回転 Y"
},
{
"Id": "ParamBodyAngleZ",
"GroupId": "ParamGroup2",
"Name": "体の回転 Z"
},
{
"Id": "ParamBodyUpper",
"GroupId": "ParamGroup2",
"Name": "上体"
},
{
"Id": "ParamBreath",
"GroupId": "ParamGroup2",
"Name": "呼吸"
},
{
"Id": "ParamBustY",
"GroupId": "ParamGroup2",
"Name": "胸 揺れ"
},
{
"Id": "ParamArmLA",
"GroupId": "ParamGroupArms",
"Name": "左腕 A"
},
{
"Id": "ParamArmRA",
"GroupId": "ParamGroupArms",
"Name": "右腕 A"
},
{
"Id": "ParamArmLB",
"GroupId": "ParamGroupArms",
"Name": "右腕 B"
},
{
"Id": "ParamArmRB",
"GroupId": "ParamGroupArms",
"Name": "左腕 B"
},
{
"Id": "ParamHandChangeR",
"GroupId": "ParamGroupArms",
"Name": "右手切替"
},
{
"Id": "ParamHandAngleR",
"GroupId": "ParamGroupArms",
"Name": "右手首角度"
},
{
"Id": "ParamHandDhangeL",
"GroupId": "ParamGroupArms",
"Name": "左手切替"
},
{
"Id": "ParamHandAngleL",
"GroupId": "ParamGroupArms",
"Name": "左手首角度"
},
{
"Id": "ParamHairFront",
"GroupId": "ParamGroup3",
"Name": "髪揺れ 前"
},
{
"Id": "ParamHairSide",
"GroupId": "ParamGroup3",
"Name": "髪揺れ 横"
},
{
"Id": "ParamHairBack",
"GroupId": "ParamGroup3",
"Name": "髪揺れ 後"
}
],
"ParameterGroups": [
{
"Id": "ParamGroupFace",
"GroupId": "",
"Name": "顔"
},
{
"Id": "ParamGroupEyes",
"GroupId": "",
"Name": "目"
},
{
"Id": "ParamGroup",
"GroupId": "",
"Name": "眉毛"
},
{
"Id": "ParamGroupMouth",
"GroupId": "",
"Name": "口"
},
{
"Id": "ParamGroup2",
"GroupId": "",
"Name": "胴体"
},
{
"Id": "ParamGroupArms",
"GroupId": "",
"Name": "腕"
},
{
"Id": "ParamGroup3",
"GroupId": "",
"Name": "髪揺れ"
}
],
"Parts": [
{
"Id": "Part01Core",
"Name": "コアパーツ"
},
{
"Id": "Part01Hoho001",
"Name": "頬"
},
{
"Id": "Part01Brow001",
"Name": "まゆ毛"
},
{
"Id": "Part01Tear",
"Name": "涙"
},
{
"Id": "Part01EyeBall001",
"Name": "目玉"
},
{
"Id": "Part01Eye001",
"Name": "目"
},
{
"Id": "Part01Nose001",
"Name": "鼻"
},
{
"Id": "Part01Mouth001",
"Name": "口"
},
{
"Id": "Part01Face001",
"Name": "顔"
},
{
"Id": "Part01Ear001",
"Name": "耳"
},
{
"Id": "Part01Neck001",
"Name": "首"
},
{
"Id": "Part01HairFront001",
"Name": "前髪"
},
{
"Id": "Part01HairSide001",
"Name": "横髪"
},
{
"Id": "Part01HairBack001",
"Name": "後ろ髪"
},
{
"Id": "Part01ArmRB001",
"Name": "左腕 B"
},
{
"Id": "Part01ArmLB001",
"Name": "右腕 B"
},
{
"Id": "Part01ArmRA001",
"Name": "右腕 A"
},
{
"Id": "Part01ArmLA001",
"Name": "左腕 A"
},
{
"Id": "Part01Body001",
"Name": "制服"
},
{
"Id": "Part01Sketch",
"Name": "[ 下絵 ]"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,246 @@
{
"Version": 3,
"FileReferences": {
"Moc": "Haru.moc3",
"Textures": [
"Haru.2048/texture_00.png",
"Haru.2048/texture_01.png"
],
"Physics": "Haru.physics3.json",
"Pose": "Haru.pose3.json",
"DisplayInfo": "Haru.cdi3.json",
"Expressions": [
{
"Name": "F01",
"File": "expressions/F01.exp3.json"
},
{
"Name": "F02",
"File": "expressions/F02.exp3.json"
},
{
"Name": "F03",
"File": "expressions/F03.exp3.json"
},
{
"Name": "F04",
"File": "expressions/F04.exp3.json"
},
{
"Name": "F05",
"File": "expressions/F05.exp3.json"
},
{
"Name": "F06",
"File": "expressions/F06.exp3.json"
},
{
"Name": "F07",
"File": "expressions/F07.exp3.json"
},
{
"Name": "F08",
"File": "expressions/F08.exp3.json"
}
],
"Motions": {
"Idle": [
{
"File": "motions/haru_g_idle.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m15.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
}
],
"TapBody": [
{
"File": "motions/haru_g_m26.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3,
"Sound": "sounds/haru_talk_13.wav"
},
{
"File": "motions/haru_g_m06.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3,
"Sound": "sounds/haru_Info_14.wav"
},
{
"File": "motions/haru_g_m20.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3,
"Sound": "sounds/haru_normal_6.wav"
},
{
"File": "motions/haru_g_m09.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
}
],
"Dance": [
{
"File": "motions/haru_g_m01.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m02.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m03.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m04.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m05.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m06.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m07.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m08.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m09.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m10.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m11.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m12.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m13.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m14.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m15.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m16.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m17.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m18.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m19.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m20.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m21.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m22.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m23.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m24.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m25.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
},
{
"File": "motions/haru_g_m26.motion3.json",
"FadeInTime": 0.3,
"FadeOutTime": 0.3
}
]
},
"UserData": "Haru.userdata3.json"
},
"Groups": [
{
"Target": "Parameter",
"Name": "EyeBlink",
"Ids": [
"ParamEyeLOpen",
"ParamEyeROpen"
]
},
{
"Target": "Parameter",
"Name": "LipSync",
"Ids": [
"ParamMouthOpenY"
]
}
],
"HitAreas": [
{
"Id": "HitArea",
"Name": "Head"
},
{
"Id": "HitArea2",
"Name": "Body"
}
]
}

View File

@ -0,0 +1,373 @@
{
"Version": 3,
"Meta": {
"PhysicsSettingCount": 4,
"TotalInputCount": 14,
"TotalOutputCount": 4,
"VertexCount": 8,
"EffectiveForces": {
"Gravity": {
"X": 0,
"Y": -1
},
"Wind": {
"X": 0,
"Y": 0
}
},
"PhysicsDictionary": [
{
"Id": "PhysicsSetting1",
"Name": "前髪"
},
{
"Id": "PhysicsSetting2",
"Name": "横髪"
},
{
"Id": "PhysicsSetting3",
"Name": "後ろ髪"
},
{
"Id": "PhysicsSetting4",
"Name": "スカーフ"
}
]
},
"PhysicsSettings": [
{
"Id": "PhysicsSetting1",
"Input": [
{
"Source": {
"Target": "Parameter",
"Id": "ParamAngleX"
},
"Weight": 60,
"Type": "X",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamAngleZ"
},
"Weight": 60,
"Type": "Angle",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamBodyAngleX"
},
"Weight": 40,
"Type": "X",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamBodyAngleZ"
},
"Weight": 40,
"Type": "Angle",
"Reflect": false
}
],
"Output": [
{
"Destination": {
"Target": "Parameter",
"Id": "ParamHairFront"
},
"VertexIndex": 1,
"Scale": 1.821,
"Weight": 100,
"Type": "Angle",
"Reflect": false
}
],
"Vertices": [
{
"Position": {
"X": 0,
"Y": 0
},
"Mobility": 1,
"Delay": 1,
"Acceleration": 1,
"Radius": 0
},
{
"Position": {
"X": 0,
"Y": 8
},
"Mobility": 0.95,
"Delay": 0.8,
"Acceleration": 1.5,
"Radius": 8
}
],
"Normalization": {
"Position": {
"Minimum": -10,
"Default": 0,
"Maximum": 10
},
"Angle": {
"Minimum": -10,
"Default": 0,
"Maximum": 10
}
}
},
{
"Id": "PhysicsSetting2",
"Input": [
{
"Source": {
"Target": "Parameter",
"Id": "ParamAngleX"
},
"Weight": 60,
"Type": "X",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamAngleZ"
},
"Weight": 60,
"Type": "Angle",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamBodyAngleX"
},
"Weight": 40,
"Type": "X",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamBodyAngleZ"
},
"Weight": 40,
"Type": "Angle",
"Reflect": false
}
],
"Output": [
{
"Destination": {
"Target": "Parameter",
"Id": "ParamHairSide"
},
"VertexIndex": 1,
"Scale": 1.593,
"Weight": 100,
"Type": "Angle",
"Reflect": false
}
],
"Vertices": [
{
"Position": {
"X": 0,
"Y": 0
},
"Mobility": 1,
"Delay": 1,
"Acceleration": 1,
"Radius": 0
},
{
"Position": {
"X": 0,
"Y": 8
},
"Mobility": 0.95,
"Delay": 0.8,
"Acceleration": 1,
"Radius": 8
}
],
"Normalization": {
"Position": {
"Minimum": -10,
"Default": 0,
"Maximum": 10
},
"Angle": {
"Minimum": -10,
"Default": 0,
"Maximum": 10
}
}
},
{
"Id": "PhysicsSetting3",
"Input": [
{
"Source": {
"Target": "Parameter",
"Id": "ParamAngleX"
},
"Weight": 60,
"Type": "X",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamAngleZ"
},
"Weight": 60,
"Type": "Angle",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamBodyAngleX"
},
"Weight": 40,
"Type": "X",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamBodyAngleZ"
},
"Weight": 40,
"Type": "Angle",
"Reflect": false
}
],
"Output": [
{
"Destination": {
"Target": "Parameter",
"Id": "ParamHairBack"
},
"VertexIndex": 1,
"Scale": 1.943,
"Weight": 100,
"Type": "Angle",
"Reflect": false
}
],
"Vertices": [
{
"Position": {
"X": 0,
"Y": 0
},
"Mobility": 1,
"Delay": 1,
"Acceleration": 1,
"Radius": 0
},
{
"Position": {
"X": 0,
"Y": 8
},
"Mobility": 0.95,
"Delay": 0.8,
"Acceleration": 1.5,
"Radius": 8
}
],
"Normalization": {
"Position": {
"Minimum": -10,
"Default": 0,
"Maximum": 10
},
"Angle": {
"Minimum": -10,
"Default": 0,
"Maximum": 10
}
}
},
{
"Id": "PhysicsSetting4",
"Input": [
{
"Source": {
"Target": "Parameter",
"Id": "ParamBodyAngleX"
},
"Weight": 100,
"Type": "X",
"Reflect": false
},
{
"Source": {
"Target": "Parameter",
"Id": "ParamBodyAngleZ"
},
"Weight": 100,
"Type": "Angle",
"Reflect": false
}
],
"Output": [
{
"Destination": {
"Target": "Parameter",
"Id": "ParamScarf"
},
"VertexIndex": 1,
"Scale": 0.873,
"Weight": 100,
"Type": "Angle",
"Reflect": false
}
],
"Vertices": [
{
"Position": {
"X": 0,
"Y": 0
},
"Mobility": 1,
"Delay": 1,
"Acceleration": 1,
"Radius": 0
},
{
"Position": {
"X": 0,
"Y": 10
},
"Mobility": 0.9,
"Delay": 0.6,
"Acceleration": 1.5,
"Radius": 10
}
],
"Normalization": {
"Position": {
"Minimum": -10,
"Default": 0,
"Maximum": 10
},
"Angle": {
"Minimum": -10,
"Default": 0,
"Maximum": 10
}
}
}
]
}

View File

@ -0,0 +1,25 @@
{
"Type": "Live2D Pose",
"Groups": [
[
{
"Id": "Part01ArmRA001",
"Link": []
},
{
"Id": "Part01ArmRB001",
"Link": []
}
],
[
{
"Id": "Part01ArmLA001",
"Link": []
},
{
"Id": "Part01ArmLB001",
"Link": []
}
]
]
}

View File

@ -0,0 +1,24 @@
{
"Version": 3,
"Meta": {
"UserDataCount": 3,
"TotalUserDataSize": 9
},
"UserData": [
{
"Target": "ArtMesh",
"Id": "D_PSD_27",
"Value": "tai"
},
{
"Target": "ArtMesh",
"Id": "D_PSD_25",
"Value": "tai"
},
{
"Target": "ArtMesh",
"Id": "D_PSD_24",
"Value": "tai"
}
]
}

View File

@ -0,0 +1,10 @@
{
"Type": "Live2D Expression",
"Parameters": [
{
"Id": "ParamMouthForm",
"Value": 0.27,
"Blend": "Add"
}
]
}

View File

@ -0,0 +1,35 @@
{
"Type": "Live2D Expression",
"Parameters": [
{
"Id": "ParamBrowLY",
"Value": -1,
"Blend": "Add"
},
{
"Id": "ParamBrowRY",
"Value": -1,
"Blend": "Add"
},
{
"Id": "ParamBrowLForm",
"Value": 1,
"Blend": "Add"
},
{
"Id": "ParamBrowRForm",
"Value": 1,
"Blend": "Add"
},
{
"Id": "ParamMouthOpenY",
"Value": 1,
"Blend": "Add"
},
{
"Id": "ParamEyeForm",
"Value": 0.54,
"Blend": "Add"
}
]
}

Some files were not shown because too many files have changed in this diff Show More