ヌルの位置に応じて曲線のパスを作る
https://gyazo.com/3e0a6469937b101183154429f16ce3e7
https://scrapbox.io/files/627d332f34b50c001dc058d1.png
https://scrapbox.io/files/627e376c1b663e001d1ca58f.png
Aeのエクスプレッションで、特定のヌルの位置を通る曲線をシェイプレイヤーで作る試み
ベジェハンドルの角度は、前後のヌルとの角度に応じて決まる。
https://gyazo.com/14c860100aad20ec16df28e69b5df046
以下のエフェクトを各レイヤーに適用しないと動かない。(面倒)
シェイプレイヤー
パス
パス名に任意の文字列を指定する。
エフェクト
1. スライダー制御 "handleLength" ... ハンドルの長さ。100で前後のヌルとの距離と等しくなる。0にするとパスが直線になる
2. チェックボックス制御 "closed" ... 最初と最後のヌルを曲線で繋ぐ(オフの場合、最初と最後のハンドルは長さは0)
3. レイヤー制御 "(パス名と前方一致する名称)" ... ヌルを指定する。アンカーの個数分エフェクトを追加する
個々のヌルレイヤー
エフェクト
1. チェックボックス制御 "corner" ... 個別でハンドルの長さを0にする
https://scrapbox.io/files/639dba53dd4678001db35d7b.png
パスに書くエクスプレッション(英語版でしか動かない気がする)
code:js
let arr = [];
let cornerNullIdx = 0;
let cornerNullIdxArr = [];
let iArr = [];
let oArr = [];
const handleLength = effect("handleLength")("Slider");
const closed = effect("closed")("Checkbox").value;
const searchStr = thisProperty.propertyGroup(1).name;
const compSize = {w: thisComp.width, h: thisComp.height};
let fxCount = 0; //エフェクトの総数
try{
while (thisLayer.effect(fxCount+1)){
fxCount++;
}
}catch(e){
}
for(let i=0; i < fxCount; i++){
const fx = thisLayer.effect(i+1);
if(fx.name.indexOf(searchStr) == 0){ // 前方一致
const p = fx("ADBE Layer Control-0001").toComp(fx("ADBE Layer Control-0001").transform.anchorPoint);
arr.push([p0 - compSize.w/2, p1 - compSize.h/2]); if(fx("ADBE Layer Control-0001").effect("corner")("Checkbox").value){
cornerNullIdxArr.push(cornerNullIdx);
}
cornerNullIdx++;
}
}
for(let i=0; i < arr.length; i++){
const prevIdx = (i-1 + arr.length) % arr.length;
const nextIdx = (i+1) % arr.length;
const p = {x: arri0, y: arri1}; const prevDist = Math.sqrt(Math.pow(prevP.x - p.x, 2) + Math.pow(prevP.y - p.y, 2));
const nextDist = Math.sqrt(Math.pow(nextP.x - p.x, 2) + Math.pow(nextP.y - p.y, 2));
const prevRad = Math.atan2(prevP.y - p.y, prevP.x - p.x); // pを原点としたprevPへの角度
const nextRad = Math.atan2(nextP.y - p.y, nextP.x - p.x);
const radSign = Math.sign(prevRad - nextRad);
iArr.push([
Math.cos((prevRad + nextRad)/2 + radSign * 90*(Math.PI / 180)) * prevDist * handleLength/100,
Math.sin((prevRad + nextRad)/2 + radSign * 90*(Math.PI / 180)) * prevDist * handleLength/100,
]);
oArr.push([
Math.cos((prevRad + nextRad)/2 - radSign * 90*(Math.PI / 180)) * nextDist * handleLength/100,
Math.sin((prevRad + nextRad)/2 - radSign * 90*(Math.PI / 180)) * nextDist * handleLength/100,
]);
}
for(let i=0; i < cornerNullIdxArr.length; i++){
iArr.splice(cornerNullIdxArri, 1, 0,0); oArr.splice(cornerNullIdxArri, 1, 0,0); }
if(!closed){
iArr.splice(arr.length-1, 1, 0,0); oArr.splice(arr.length-1, 1, 0,0); }
createPath(arr, inTangents = iArr, outTangents = oArr, isClosed = closed);
ヌルの動かし方によっては動きが滑らかに繋がらない場合がある。
https://gyazo.com/b8bed3f6012bd9daf0181ed7165641c8
シェイプレイヤーのパスにキーフレームを打つよりもなめらかな動かし方ができそう。
ヌルにbounceの動きさせて使えそう
https://gyazo.com/1b2dd712d469e2eca2206073532ceccf
ThiccStrokeと組み合わせて使えそう
https://gyazo.com/7c2e220185d104e51af58cd03a367349
ThiccStroke
Create Nulls From Path Extendedとも併用できそう
https://gyazo.com/229853d4fd7c344d2c2feb896d5ab54f
Create Nulls From Path Extended
これもヌルでパスを制御するスクリプト。引いたパスからヌルを生成できる。
パスに沿ってヌルを配置する機能があって、こっちと併用できそう
3Dレイヤーのヌルに対応して、ハンドルの位置を3D空間上で決めるパターンも一応できた
※ヌルを3Dにしないとエラーが出る
https://gyazo.com/b7c8add268ffd41bf95ddca12946ff19
code:js
let arr = [];
let pArr = [];
let nullIdxArr = [];
let cornerNullIdx = 0;
let cornerNullIdxArr = [];
let iArr = [];
let oArr = [];
const handleLength = effect("handleLength")("Slider");
const closed = effect("closed")("Checkbox").value;
const searchStr = thisProperty.propertyGroup(1).name;
const compSize = {w: thisComp.width, h: thisComp.height};
let fxCount = 0; //エフェクトの総数
try{
while (thisLayer.effect(fxCount+1)){
fxCount++;
}
}catch(e){
}
for(let i=0; i < fxCount; i++){
const fx = thisLayer.effect(i+1);
if(fx.name.indexOf(searchStr) == 0){ // 前方一致
const p = fx("ADBE Layer Control-0001").transform.position;
const pathP = fx("ADBE Layer Control-0001").toComp(fx("ADBE Layer Control-0001").transform.anchorPoint);
pArr.push(p);
arr.push([pathP0 - compSize.w/2, pathP1 - compSize.h/2]); nullIdxArr.push(i+1);
if(fx("ADBE Layer Control-0001").effect("corner")("Checkbox").value){
cornerNullIdxArr.push(cornerNullIdx);
}
cornerNullIdx++;
}
}
for(let i=0; i < arr.length; i++){
const prevIdx = (i-1 + arr.length) % arr.length;
const nextIdx = (i+1) % arr.length;
const p = {x: pArri0, y: pArri1, z: pArri2}; const prevDist = Math.sqrt(Math.pow(prevP.x - p.x, 2) + Math.pow(prevP.y - p.y, 2) + Math.pow(prevP.z - p.z, 2));
const nextDist = Math.sqrt(Math.pow(nextP.x - p.x, 2) + Math.pow(nextP.y - p.y, 2) + Math.pow(nextP.z - p.z, 2));
const unitDiff = pOfP2prevPUnit - pOfP2nextPUnit;
const unitDist = Math.sqrt(Math.pow(unitDiff0, 2) + Math.pow(unitDiff1, 2) + Math.pow(unitDiff2, 2)); //原点からunitDiff分移動したときの距離 const unit = (unitDist !== 0) ? unitDiff / unitDist : 0, 0, 0; // inTangent方向に1移動する分 const pOfHandleI = unit * prevDist * handleLength/100; // 3D空間上でinTangentが来てほしい位置
const pOfHandleO = - unit * nextDist * handleLength/100;
const fx = thisLayer.effect(nullIdxArri); const handleI = fx("ADBE Layer Control-0001").toComp(pOfHandleI); //3D空間上でpOfHandleIの値X,Y,Zが、平面上だとX,Y,hoge const handleO = fx("ADBE Layer Control-0001").toComp(pOfHandleO);
iArr.push([handleI0 - arri0 - compSize.w/2, handleI1 - arri1 - compSize.h/2]); oArr.push([handleO0 - arri0 - compSize.w/2, handleO1 - arri1 - compSize.h/2]); }
for(let i=0; i < cornerNullIdxArr.length; i++){
iArr.splice(cornerNullIdxArri, 1, 0,0); oArr.splice(cornerNullIdxArri, 1, 0,0); }
if(!closed){
iArr.splice(arr.length-1, 1, 0,0); oArr.splice(arr.length-1, 1, 0,0); }
createPath(arr, inTangents = iArr, outTangents = oArr, isClosed = closed);
(このコードだと、ハンドルの位置が個々のヌルの回転の影響を受けてしまう)
大抵3Dソフトや別エフェクトでやったほうが良いなと思う例になってしまい、活かし方が難しいかも。
むしろ3D空間にヌルを配置して、ハンドルはtoCompを使って2D的に設定する方が意外性があって面白いのでは(↓例)
https://gyazo.com/e6fca771b3f3955fab03da4397214440
遠近感あるのかないのか謎
一旦上記の諸々設定済のヌルとシェイプレイヤーを追加するスクリプトを作った