From 8ac72d40bc0a22eb06742c7062614457589bc822 Mon Sep 17 00:00:00 2001 From: ito Date: Thu, 13 May 2021 11:59:55 +0900 Subject: [PATCH] [Beta version] Update to Cubism 4 SDK for Web R3 Beta1 --- CHANGELOG.md | 8 ++ LICENSE.md | 6 ++ README.md | 6 +- src/math/cubismmath.ts | 134 +++++++++++++++++++++++++++++++++ src/motion/cubismmotion.ts | 127 ++++++++++++++++++++++++++++++- src/motion/cubismmotionjson.ts | 22 ++++++ 6 files changed, 298 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c02a9d7..673b95a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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/). +## [4-r.3-beta.1] - 2021-05-13 + +### Added + +* Implement a function to get the correct value when the time axis of the Bezier handle cannot be linear. + + ## [4-r.2] - 2021-03-09 ### Fixed @@ -43,5 +50,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Reformat code using Prettier and ESLint. +[4-r.3-beta.1]: https://github.com/Live2D/CubismWebFramework/compare/4-r.2...4-r.3-beta.1 [4-r.2]: https://github.com/Live2D/CubismWebFramework/compare/4-r.1...4-r.2 [4-r.1]: https://github.com/Live2D/CubismWebFramework/compare/ce2585a919ac6e99f64dd468933772c6f1abbcc7...4-r.1 diff --git a/LICENSE.md b/LICENSE.md index d056d8d..bd0be75 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -6,6 +6,7 @@ Cubism Web Framework is included in Live2D Cubism Components. Cubism Web Framework は Live2D Cubism Components に含まれます。 +Cubism Web Framework 包括在 Live2D Cubism Components 中。 ## Cubism SDK Release License @@ -17,6 +18,9 @@ Cubism Web Framework は Live2D Cubism Components に含まれます。 * [Cubism SDK リリースライセンス](https://www.live2d.com/ja/download/cubism-sdk/release-license/) +如果您的企业在最近一个会计年度的销售额达到或超过1000万日元,您必须得到Cubism SDK的出版授权许可(出版许可协议)。 + +* [Cubism SDK发行许可证](https://www.live2d.com/zh-CHS/download/cubism-sdk/release-license/) ## Live2D Open Software License @@ -24,6 +28,7 @@ Live2D Cubism Components is available under Live2D Open Software License. * [Live2D Open Software License](https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html) * [Live2D Open Software 使用許諾契約書](https://www.live2d.com/eula/live2d-open-software-license-agreement_jp.html) +* [Live2D Open Software 使用授权协议](https://www.live2d.com/eula/live2d-open-software-license-agreement_cn.html) ## Live2D Proprietary Software License @@ -32,6 +37,7 @@ Live2D Cubism Core is available under Live2D Proprietary Software License. * [Live2D Proprietary Software License Agreement](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html) * [Live2D Proprietary Software 使用許諾契約書](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_jp.html) +* [Live2D Proprietary Software 使用授权协议](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_cn.html) --- diff --git a/README.md b/README.md index 0d89ee2..5edd669 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cubism Web Framework +# \[Beta Version\] Cubism Web Framework Live2D Cubism 4 Editor で出力したモデルをアプリケーションで利用するためのフレームワークです。 @@ -7,6 +7,10 @@ Live2D Cubism 4 Editor で出力したモデルをアプリケーションで利 ビルドを行うことで、ブラウザで利用可能な JavaScript ライブラリとして利用することができます。 +**本 SDK は、 Beta バージョンとなります。先行して新機能を取り込んでいるため、不安定な挙動を示す場合がございます。安定した製品をお求めの方は、公式サイトから配布されている正式版のパッケージ又は `develop` `master` ブランチをご利用ください。** + +**\[Beta Version\] の SDK の不具合、各種ご意見等に関しましては、 Live2D コミュニティ にてご連絡ください。直接のコードに対する指摘、修正等は、直接 Pull requests としてご投稿ください。** + ## ライセンス diff --git a/src/math/cubismmath.ts b/src/math/cubismmath.ts index 704c311..fef3b6b 100644 --- a/src/math/cubismmath.ts +++ b/src/math/cubismmath.ts @@ -11,6 +11,8 @@ import { CubismVector2 } from './cubismvector2'; * 数値計算などに使用するユーティリティクラス */ export class CubismMath { + static readonly Epsilon: number = 0.00001; + /** * 第一引数の値を最小値と最大値の範囲に収めた値を返す * @@ -68,6 +70,33 @@ export class CubismMath { 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; + } + /** * イージング処理されたサインを求める * フェードイン・アウト時のイージングに利用できる @@ -185,6 +214,111 @@ export class CubismMath { return ret; } + /** + * 三次方程式の三次項の係数が0になったときに補欠的に二次方程式の解をもとめる。 + * a * x^2 + b * x + c = 0 + * + * @param a -> 二次項の係数値 + * @param b -> 一次項の係数値 + * @param c -> 定数項の値 + * @return 二次方程式の解 + */ + static quadraticEquation(a: number, b: number, c: number): number { + if (this.abs(a) < CubismMath.Epsilon) { + if (this.abs(b) < CubismMath.Epsilon) { + return -c; + } + return -c / b; + } + + return -(b + this.sqrt(b * b - 4.0 * a * c)) / (2.0 * a); + } + + /** + * カルダノの公式によってベジェのt値に該当する3次方程式の解を求める。 + * 重解になったときには0.0~1.0の値になる解を返す。 + * + * a * x^3 + b * x^2 + c * x + d = 0 + * + * @param a -> 三次項の係数値 + * @param b -> 二次項の係数値 + * @param c -> 一次項の係数値 + * @param d -> 定数項の値 + * @return 0.0~1.0の間にある解 + */ + static cardanoAlgorithmForBezier( + a: number, + b: number, + c: number, + d: number + ): number { + if (this.sqrt(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); + } + /** * コンストラクタ */ diff --git a/src/motion/cubismmotion.ts b/src/motion/cubismmotion.ts index 17c5e05..c420837 100644 --- a/src/motion/cubismmotion.ts +++ b/src/motion/cubismmotion.ts @@ -22,7 +22,7 @@ import { CubismMotionSegment, CubismMotionSegmentType } from './cubismmotioninternal'; -import { CubismMotionJson } from './cubismmotionjson'; +import { CubismMotionJson, EvaluationOptionFlag } from './cubismmotionjson'; import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; const EffectNameEyeBlink = 'EyeBlink'; @@ -31,6 +31,11 @@ const TargetNameModel = 'Model'; const TargetNameParameter = 'Parameter'; const TargetNamePartOpacity = 'PartOpacity'; +/** + * Cubism SDK R2 以前のモーションを再現させるなら true 、アニメータのモーションを正しく再現するなら false 。 + */ +const UseOldBeziersCurveMotion = false; + function lerpPoints( a: CubismMotionPoint, b: CubismMotionPoint, @@ -71,6 +76,109 @@ function bezierEvaluate(points: CubismMotionPoint[], time: number): number { return lerpPoints(p012, p123, t).value; } +function bezierEvaluateBinarySearch( + points: CubismMotionPoint[], + time: number +): number { + const x_error = 0.01; + + const x: number = time; + let x1: number = points[0].time; + let x2: number = points[3].time; + let cx1: number = points[1].time; + let cx2: number = points[2].time; + + let ta = 0.0; + let tb = 1.0; + let t = 0.0; + let i = 0; + + for (let var33 = true; i < 20; ++i) { + if (x < x1 + x_error) { + t = ta; + break; + } + + if (x2 - x_error < x) { + t = tb; + break; + } + + let centerx: number = (cx1 + cx2) * 0.5; + cx1 = (x1 + cx1) * 0.5; + cx2 = (x2 + cx2) * 0.5; + const ctrlx12: number = (cx1 + centerx) * 0.5; + const ctrlx21: number = (cx2 + centerx) * 0.5; + centerx = (ctrlx12 + ctrlx21) * 0.5; + if (x < centerx) { + tb = (ta + tb) * 0.5; + if (centerx - x_error < x) { + t = tb; + break; + } + + x2 = centerx; + cx2 = ctrlx12; + } else { + ta = (ta + tb) * 0.5; + if (x < centerx + x_error) { + t = ta; + break; + } + + x1 = centerx; + cx1 = ctrlx21; + } + } + + if (i == 20) { + t = (ta + tb) * 0.5; + } + + if (t < 0.0) { + t = 0.0; + } + if (t > 1.0) { + t = 1.0; + } + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; +} + +function bezierEvaluateCardanoInterpretation( + points: CubismMotionPoint[], + time: number +): number { + const x: number = time; + const x1: number = points[0].time; + const x2: number = points[3].time; + const cx1: number = points[1].time; + const cx2: number = points[2].time; + + const a: number = x2 - 3.0 * cx2 + 3.0 * cx1 - x1; + const b: number = 3.0 * cx2 - 6.0 * cx1 + 3.0 * x1; + const c: number = 3.0 * cx1 - 3.0 * x1; + const d: number = x1 - x; + + const t: number = CubismMath.cardanoAlgorithmForBezier(a, b, c, d); + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; +} + function steppedEvaluate(points: CubismMotionPoint[], time: number): number { return points[0].value; } @@ -616,6 +724,10 @@ export class CubismMotion extends ACubismMotion { this._motionData.fps = json.getMotionFps(); this._motionData.eventCount = json.getEventCount(); + const areBeziersRestructed: boolean = json.getEvaluationOptionFlag( + EvaluationOptionFlag.EvaluationOptionFlag_AreBeziersRistricted + ); + if (json.isExistMotionFadeInTime()) { this._fadeInSeconds = json.getMotionFadeInTime() < 0.0 ? 1.0 : json.getMotionFadeInTime(); @@ -750,9 +862,16 @@ export class CubismMotion extends ACubismMotion { case CubismMotionSegmentType.CubismMotionSegmentType_Bezier: { this._motionData.segments.at(totalSegmentCount).segmentType = CubismMotionSegmentType.CubismMotionSegmentType_Bezier; - this._motionData.segments.at( - totalSegmentCount - ).evaluate = bezierEvaluate; + + if (areBeziersRestructed || UseOldBeziersCurveMotion) { + this._motionData.segments.at( + totalSegmentCount + ).evaluate = bezierEvaluate; + } else { + this._motionData.segments.at( + totalSegmentCount + ).evaluate = bezierEvaluateCardanoInterpretation; + } this._motionData.points.at( totalPointCount diff --git a/src/motion/cubismmotionjson.ts b/src/motion/cubismmotionjson.ts index 30d3ec6..2ae9052 100644 --- a/src/motion/cubismmotionjson.ts +++ b/src/motion/cubismmotionjson.ts @@ -14,6 +14,7 @@ import { CubismJson } from '../utils/cubismjson'; const Meta = 'Meta'; const Duration = 'Duration'; const Loop = 'Loop'; +const AreBeziersRestricted = 'AreBeziersRestricted'; const CurveCount = 'CurveCount'; const Fps = 'Fps'; const TotalSegmentCount = 'TotalSegmentCount'; @@ -75,6 +76,20 @@ export class CubismMotionJson { .toBoolean(); } + public getEvaluationOptionFlag(flagType: number): boolean { + if ( + EvaluationOptionFlag.EvaluationOptionFlag_AreBeziersRistricted == flagType + ) { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(AreBeziersRestricted) + .toBoolean(); + } + + return false; + } + /** * モーションカーブの個数の取得 * @return モーションカーブの個数 @@ -352,6 +367,13 @@ export class CubismMotionJson { _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