[AE表達(dá)式]使用createPath創(chuàng)建與控制曲線(xiàn)_Part.3

·這篇文章來(lái)介紹下用AE生成程序曲線(xiàn),然后制作可以正確嚙合的齒輪形狀,沒(méi)了解createPath這個(gè)函數(shù)的可以先看下前兩篇文章
createPath基本用法:

createPath切線(xiàn)使用方法:

程序曲線(xiàn)就是利用曲線(xiàn)的坐標(biāo)公式直接在AE中生成它的形狀,比如AE自帶的橢圓,矩形,多邊形都是程序曲線(xiàn),我們直接調(diào)節(jié)它的參數(shù)就能控制它的形狀而不必自己調(diào)點(diǎn)

想要自定義程序曲線(xiàn)的話(huà)需要能代表曲線(xiàn)的公式,但是曲線(xiàn)公式分一般方程和參數(shù)方程兩種,關(guān)于參數(shù)方程可以先百度一下看看定義

用最簡(jiǎn)單的圓來(lái)說(shuō),圓的普通方程是x2+y2=r2,我們是無(wú)法用這個(gè)方程來(lái)連續(xù)生成圓上每個(gè)點(diǎn)的,但圓還有其他的表示方式:

如果用圓的參數(shù)方程就可以用θ一個(gè)值來(lái)表示圓上θ處的點(diǎn)的坐標(biāo):

(這個(gè)動(dòng)畫(huà)是我用AE生成的所以Y軸坐標(biāo)也是反的)
當(dāng)然我們?cè)诓患尤肭芯€(xiàn)的情況下不會(huì)生成這么圓滑的圓的,因?yàn)榍€(xiàn)本身是無(wú)限個(gè)點(diǎn)的集合,但我們只能使用有限的點(diǎn)來(lái)表示曲線(xiàn),當(dāng)然這個(gè)問(wèn)題用切線(xiàn)也很好解決,后面再說(shuō)
現(xiàn)在我們來(lái)手動(dòng)生成一個(gè)圓試試,先觀察圓的參數(shù)方程,注意到我們需要的是r和θ兩個(gè)參數(shù),r代表圓的半徑,θ是在它某角度上的點(diǎn)
創(chuàng)建空的形狀層,添加一個(gè)path和一個(gè)strok,在path的表達(dá)式中先定義一個(gè)需要兩個(gè)參數(shù)的函數(shù)
function circle(r, theta) {
? var x = r * Math.cos(theta);
? var y = r * Math.sin(theta);
? return [x, y];
}
然后我們?cè)谶@個(gè)形狀層上建立一個(gè)滑塊控制,重命名為r,把值改成200,建立一個(gè)變量拉皮筋連給r這個(gè)滑塊,暫時(shí)先寫(xiě)成下面這樣

然后我們通過(guò)這個(gè)circle函數(shù)生成四個(gè)點(diǎn),分別是0°,90°,180°,270°四個(gè)角度的點(diǎn),把這四個(gè)點(diǎn)裝到一個(gè)數(shù)組里,然后用這四個(gè)數(shù)先來(lái)生成曲線(xiàn)看一下
var P1 = circle(r, 0);
var P2 = circle(r, 90);
var P3 = circle(r, 180);
var P4 = circle(r, 270);
var CVS = [P1, P2, P3, P4];
createPath(points=CVS, inTangents=[], outTangents=[], is_closed=true);

雖然生成了四個(gè)點(diǎn),但是好像并不是我們需要的四個(gè)點(diǎn),這是因?yàn)槲覀冚斎氲氖墙嵌戎?,而cos和sin兩個(gè)函數(shù)需要的是弧度值,我們可以用degreesToRadians()這個(gè)函數(shù)來(lái)把角度轉(zhuǎn)化成弧度(同樣弧度轉(zhuǎn)角度就用radiansToDegrees())
var P1 = circle(r, degreesToRadians(0));
var P2 = circle(r, degreesToRadians(90));
var P3 = circle(r, degreesToRadians(180));
var P4 = circle(r, degreesToRadians(270));
將輸入的數(shù)值用這個(gè)函數(shù)像上面這樣轉(zhuǎn)化一下,再看一下生成的曲線(xiàn)

這回生成了我們需要的對(duì)應(yīng)四個(gè)角度的點(diǎn),此時(shí)如果加入切線(xiàn)的話(huà)很容易就把它變成圓形了,不過(guò)這個(gè)不是重點(diǎn),我們先試著增加點(diǎn)的數(shù)量來(lái)使曲線(xiàn)更接近圓形
如果我們想自己控制點(diǎn)的數(shù)量可以使用for循環(huán)把一定數(shù)量的點(diǎn)直接傳遞給數(shù)組中
先在圖層上建立個(gè)滑塊控制并命名為number of point, 用來(lái)控制點(diǎn)的數(shù)量,再建個(gè)滑塊控制命名為step用來(lái)控制每?jī)蓚€(gè)點(diǎn)的參數(shù)相差多少

把這兩個(gè)參數(shù)傳入表達(dá)式,然后用for循環(huán)批量生成點(diǎn),具體寫(xiě)法如下
function circle(r, theta) {
var x = r * Math.cos(theta);
var y = r * Math.sin(theta);
return [x, y];
}
var r = effect("r")("Slider");
var Numpt = effect("number of point")("Slider");
var step = effect("step")("Slider");
var CVS = new Array();
for(i = 0; i < Numpt; i++) {
? var P = circle(r, degreesToRadians(i * step));
? CVS.push(P);
}
createPath(points=CVS, inTangents=[], outTangents=[], is_closed=true);

現(xiàn)在我們可以自己控制每隔step個(gè)角度來(lái)生成numpt個(gè)點(diǎn),但是這樣的控制方式不如人意,如果我們要生成一個(gè)完整的圓,只有在step和numpt相乘為360的時(shí)候才可以,我希望這個(gè)圓一直是完整的,numpt還是點(diǎn)的數(shù)量,并且會(huì)自動(dòng)調(diào)控step讓所有點(diǎn)完成一個(gè)完整的圓,所以需要優(yōu)化一下邏輯
我們不需要自己控制step,而是讓step等于360除以點(diǎn)的數(shù)量,所以把表達(dá)式優(yōu)化為
function circle(r, theta) {
var x = r * Math.cos(theta);
var y = r * Math.sin(theta);
return [x, y];
}
var r = effect("r")("Slider");
var Numpt = effect("number of point")("Slider");
var step = 2 * Math.PI / Numpt;
var CVS = new Array();
for(i = 0; i < Numpt; i++) {
? var P = circle(r, i * step);
? CVS.push(P);
}
createPath(points=CVS, inTangents=[], outTangents=[], is_closed=true);
這樣就不需要自己控制step了,并且由于我們直接用弧度2π除以點(diǎn)數(shù),得到的也是弧度值,所以也不需要轉(zhuǎn)換角度的函數(shù)

目前來(lái)看貌似我們只是做了一個(gè)可控制邊數(shù)的正多邊形而已,AE本身就能調(diào)用多邊形,但其實(shí)我們創(chuàng)建了一個(gè)可以自定義曲線(xiàn)的方式,現(xiàn)在要得到不同的程序曲線(xiàn)只要更改相應(yīng)的曲線(xiàn)函數(shù)就可以了
比如這個(gè)外旋輪線(xiàn)

它的意思是一個(gè)小圓在大圓上滾動(dòng),圓內(nèi)的某一點(diǎn)在小圓滾動(dòng)過(guò)程中形成的路徑曲線(xiàn)(實(shí)際上點(diǎn)不需要在小圓內(nèi)部),那么根據(jù)它的定義,需要四個(gè)參數(shù),R是大圓的半徑,r是小圓的半徑,d是形成路徑的點(diǎn)距離小圓中心的距離,t就是我們需要生成曲線(xiàn)用的通用參數(shù)
(使用圓的時(shí)候使用theta作為參數(shù)比較便于理解,為了書(shū)寫(xiě)方便,后面定義函數(shù)參數(shù)的時(shí)候theta就直接寫(xiě)t了)
那么我們建立幾個(gè)滑塊控制分別對(duì)應(yīng)R,r,d,再加上一個(gè)控制點(diǎn)數(shù)的滑塊,然后把上面我們生成圓的表達(dá)式中的函數(shù)按照百科中的寫(xiě)的抄成表達(dá)式語(yǔ)言,記得for循環(huán)中調(diào)用的函數(shù)也要相應(yīng)的改成我們改過(guò)的曲線(xiàn)函數(shù),改完如下:
function curve(R, r, d, t) {
var x = (R + r) * Math.cos(t) - d * Math.cos((R + r) * t/ r);
var y = (R + r) * Math.sin(t) - d * Math.sin((R + r) * t / r);
return [x, y];
}
var Numpt = effect("number of point")("Slider");
var R = effect("R")(1);
var r = effect("r")(1);
var d = effect("d")(1);
var step = 2 * Math.PI / Numpt;
var CVS = new Array();
for (i = 0; i < Numpt; i++) {
var P = curve(R, r, d, i * step);
CVS.push(P);
}
createPath(points = CVS, inTangents = [], outTangents = [], is_closed = true);

根據(jù)不同的參數(shù)可以生成不同的樣式,但是由于它的曲線(xiàn)特性,只有R是r的整數(shù)倍的時(shí)候才能完整的讓t在0~π中完美銜接,當(dāng)然也可以改范圍讓它多轉(zhuǎn)幾圈

比如當(dāng)我設(shè)置為R=200,r=120,d=200時(shí),t的范圍要從0~6π才能讓這個(gè)曲線(xiàn)順利首尾相接
而且對(duì)于很多不閉合的曲線(xiàn)來(lái)說(shuō),隨著t不斷增大,曲線(xiàn)是無(wú)限延展的,所以我們繼續(xù)改進(jìn)一下表達(dá)式,讓它可以延展下去
比如這個(gè)阿基米德螺旋線(xiàn)

首先按照現(xiàn)有的表達(dá)式直接替換函數(shù)(注意α,β是控制形狀的參數(shù),θ是theta,也就是t)

這是t在0~2π范圍生成的曲線(xiàn),而它還可以繼續(xù)延展,并且由于是開(kāi)放曲線(xiàn),不需要把is_closed屬性設(shè)置為true,首先我們先建立一個(gè)checkbox control(中文忘了,也在表達(dá)式控制那欄里),命名為Closed,把這個(gè)參數(shù)傳入,輸入給is_closed,這樣我們可以直接使用這個(gè)按鈕來(lái)控制曲線(xiàn)是開(kāi)放還是閉合的

然后再建立一個(gè)滑塊控制命名為Endt,用這個(gè)值來(lái)除以點(diǎn)數(shù)就可以自由控制t的結(jié)束值是多少了,我為了讓這個(gè)值是100的時(shí)候t的值正好是2π,所以把他又做了個(gè)乘法映射,最終寫(xiě)法如下:
function curve(alpha, beta,? t) {
var x = (alpha + beta * t) * Math.cos(t);
var y = (alpha + beta * t) * Math.sin(t);
return [x, y];
}
var Numpt = effect("number of point")("Slider");
var a = effect("alpha")(1);
var b = effect("beta")(1);
var step = effect("Endt")(1) / Numpt * 0.2 / Math.PI;
var CVS = new Array();
for (i = 0; i < Numpt; i++) {
var P = curve(a, b, i * step);
CVS.push(P);
}
var Closed = effect("Closed")(1);
createPath(points = CVS, inTangents = [], outTangents = [], is_closed = Closed);

這回我們可以自由設(shè)置曲線(xiàn)延展的范圍了
設(shè)置好之后我們可以多試一些有趣的曲線(xiàn)試試看
拋物線(xiàn):

擺線(xiàn)(內(nèi)旋線(xiàn)和外旋線(xiàn)其實(shí)就是擺線(xiàn)的一種):

漸開(kāi)線(xiàn):

漸開(kāi)線(xiàn)這個(gè)很重要,是齒輪上的主要參數(shù)線(xiàn)
心形曲線(xiàn):

曲線(xiàn)是倒的,因?yàn)锳E坐標(biāo)是Y軸向下的,所以可以把return成[x, -y]來(lái)修正它(當(dāng)然你也可以直接Y軸縮放圖層)

關(guān)于心形曲線(xiàn)有很多不同的樣式,可以百度搜一下,我是參考的這篇文章
https://blog.csdn.net/stereohomology/article/details/51581391

然后我在這個(gè)視頻中看到這樣一個(gè)很有意思的心形曲線(xiàn)[數(shù)學(xué)之美]看完你會(huì)愛(ài)上數(shù)學(xué)的~謹(jǐn)以此片致敬高質(zhì)量科普工作者們(manim制作)

這個(gè)曲線(xiàn)會(huì)隨著系數(shù)a的增加使函數(shù)圖像越來(lái)越像心形,雖然它不是參數(shù)方程,但是我們可以令x=t來(lái)強(qiáng)行把它定義成參數(shù)方程
實(shí)際上它是一個(gè)設(shè)置了Y軸坐標(biāo)映射的正弦函數(shù),它的有效定義域是-2√2 ~ 2√2,我在這個(gè)函數(shù)最兩端生成兩個(gè)點(diǎn),中間的點(diǎn)都落在正弦函數(shù)的的π/2的倍數(shù)上(也就是πax,但這只能對(duì)應(yīng)到正弦部分的峰值,不能對(duì)應(yīng)到整個(gè)函數(shù)的峰值),所以算起x的點(diǎn)分布還是有點(diǎn)繞,具體生成方法在下面
//Controllable parameters
var size = effect("size")(1); //Total size
var n = effect("a")(1); //coefficient a
//Main function of curve
function curve(s, a, t) {
var x = t;
var y = Math.pow(Math.pow(x, 2), (1 / 3)) + 0.9 * Math.sqrt(8 - Math.pow(x, 2)) * Math.sin(Math.PI * a * x);
return [x, -y] * s;
}
//Derive parameters
var step1 = 1 / (2 * n); //x step with equal spacing
var step2 = (2 * Math.SQRT2 - 2) % step1; //x step in both sides
var numptConsOneSide = n * 4 + Math.floor((2 * Math.SQRT2 - 2) / step1); // The count of points with equal spacing in on side
var numpt = numptConsOneSide * 2 + 3; //tatal count of points
var posiPT = new Array();
for (i = 1; i <= numptConsOneSide; i++) {
posiPT.push(i * step1);
}
var negaPT = posiPT.map(p => {
return -p;
}).reverse();
var pt = [-(2 * Math.SQRT2 - 0.00000001)].concat(negaPT, [0], posiPT, [(2 * Math.SQRT2 - 0.00000001)]);
var CVS = new Array();
for (i = 0; i < numpt; i++) {
var P = curve(size, n, pt[i]);
CVS.push(P);
}
var Closed = effect("Closed")(1);
createPath(points = CVS, inTangents = [], outTangents = [], is_closed = Closed);
仔細(xì)看的話(huà)就是隨著a的增加,sin函數(shù)會(huì)讓曲線(xiàn)的波長(zhǎng)變短,使曲線(xiàn)的局部峰值數(shù)量變多

設(shè)定x取值的細(xì)節(jié)的話(huà)就是定義一個(gè)numptConsOneSide變量,讓它算出在0~2√2范圍內(nèi)能容納多少個(gè)sin函數(shù)能取到π/2整數(shù)倍的值,然后把這些值放入posiPT這個(gè)集合中,再把posiPT中每個(gè)值乘以-1,然后反轉(zhuǎn)一下數(shù)組,裝到negaPT,作為0~-2√2范圍的點(diǎn),然后把這兩個(gè)數(shù)組和-2√2、0、2√2這三個(gè)值按照大小排列再連接起來(lái),就是整個(gè)曲線(xiàn)上需要取到的x的值,只要把這些x傳遞給t求出y就可以了
你懵了嗎?反正我是挺懵的

所以我做了個(gè)動(dòng)畫(huà)演示一下x的取值過(guò)程

需要注意的是由于表達(dá)式的精度問(wèn)題,我們的值不能精確的取到-2√2和2√2這兩個(gè)點(diǎn),因?yàn)樗趯?shí)際計(jì)算的時(shí)候用的是很接近這兩個(gè)值的小數(shù),所以我把他們減去0.00000001來(lái)修正這個(gè)計(jì)算機(jī)誤差,中間有多少個(gè)0是無(wú)所謂的,只是為了讓這個(gè)數(shù)稍微偏移一下

然后我們要面對(duì)一個(gè)問(wèn)題,就是貝塞爾曲線(xiàn)的優(yōu)勢(shì)是可以用少量的點(diǎn)描述一個(gè)比較復(fù)雜的曲線(xiàn),為此要在AE中為它設(shè)置手柄,之前我們完全是靠增加點(diǎn)的數(shù)量來(lái)使曲線(xiàn)盡量顯示得平滑的,雖然我們可以在曲線(xiàn)上點(diǎn)擊右鍵,選擇rotoBezier,但是這種方式獲得的平滑曲線(xiàn)在點(diǎn)數(shù)較少的情況下非常不精確,如果為了精確度再增加曲線(xiàn)點(diǎn)的話(huà)就沒(méi)有意義了

所以我們要讓程序自動(dòng)計(jì)算函數(shù)的切線(xiàn),而函數(shù)的導(dǎo)數(shù)就代表了函數(shù)曲線(xiàn)的切線(xiàn),這個(gè)在百度上就能查到

最簡(jiǎn)單的圓的函數(shù)時(shí)x=sinθ, y=cosθ
根據(jù)導(dǎo)數(shù)公式,圓的導(dǎo)函數(shù)就是x=cosθ,y=-sinθ
那么我們定義一個(gè)圓的函數(shù),再定義一個(gè)圓的導(dǎo)函數(shù),把導(dǎo)函數(shù)的值加上圓的函數(shù),得出的值和原函數(shù)得出的值作為數(shù)組傳遞給createPath的point屬性,順便把這條線(xiàn)的長(zhǎng)度乘以一個(gè)系數(shù),不然在AE中會(huì)因?yàn)樘炭床灰?jiàn)
function circle(r, theta){
? x = Math.sin(theta);
y = Math.cos(theta);
return [x, y] * r;
}
function dCircle(theta) {
x = Math.cos(theta);
y = -Math.sin(theta);
return [x, y];
}
var r = effect("r")(1);
var sLength = effect("Length Multi")(1) * 0.1;
var theta = effect("theta")(1);
var pos = circle(r, degreesToRadians(theta));
var tan = pos + dCircle(degreesToRadians(theta)) * sLength;
createPath(points = [pos, tan], inTangents = [], outTangents = [], is_closed = 0);

通過(guò)導(dǎo)數(shù),我們可以獲得任意theta值上的圓的點(diǎn)的切線(xiàn)方向
同樣,我們能得到已知公式的曲線(xiàn)上任意一點(diǎn)的切線(xiàn)方向,那么我們來(lái)試試正弦曲線(xiàn),先按照之前的方法生成一個(gè)基于點(diǎn)數(shù)量的正弦函數(shù)曲線(xiàn)表達(dá)式
function toPara(r, t) {
var x = t;
var y = Math.sin(t);
return [x, -y] * r;
}
var ptArray = new Array();
var numpt = effect("Points Amount")(1);
var size = effect("Size")(1);
var closure = effect("Close")(1);
var sLength = effect("Length Multi")(1) * 0.1;
if (numpt > 0) {
for (i = 0; i < numpt; i++) {
step = 2 * Math.PI / (numpt);
ptArray.push(toPara(size, (step * i * sLength)));
}
} else {
ptArray.push([0, 0]);
}
createPath(points = ptArray, inTangents = [], outTangents = [], is_closed = closure);


當(dāng)點(diǎn)數(shù)量不夠多的時(shí)候,我們只能得到一段棱角分明的線(xiàn),然后新定義一個(gè)函數(shù),根據(jù)導(dǎo)數(shù)公式,導(dǎo)函數(shù)寫(xiě)法如下
function dPara(l, t) {
var dx = 1;
var dy = Math.cos(t);
return [dx, -dy] * l;
}
它的第一個(gè)參數(shù)l,也是為了加長(zhǎng)切線(xiàn)長(zhǎng)度設(shè)置的,我們可以新建一個(gè)滑塊控制把參數(shù)傳入l,這樣可以手動(dòng)控制切線(xiàn)大小,然后同樣使用for循環(huán)把切線(xiàn)裝入數(shù)組中傳遞給createPath的outTangents屬性中,完整寫(xiě)法如下
function toPara(r, t) {
var x = t;
var y = Math.sin(t);
return [x, -y] * r;
}
function dPara(l, t) {
var dx = 1;
var dy = Math.cos(t);
return [dx, -dy] * l;
}
var ptArray = new Array();
var numpt = effect("Points Amount")(1);
var size = effect("Size")(1);
var closure = effect("Close")(1);
var sLength = effect("Length Multi")(1) * 0.1;
var tanLen = effect("Tangent Length")(1);
for (i = 0; i < numpt; i++) {
step = 2 * Math.PI / (numpt);
ptArray.push(toPara(size, (step * i * sLength)));
}
var outTans = new Array();
for (i = 0; i < numpt; i++) {
step = 2 * Math.PI / (numpt);
outTans.push(dPara(tanLen, (step * i * sLength)));
}
createPath(points = ptArray, inTangents = [], outTangents = outTans, is_closed = closure);

現(xiàn)在每個(gè)曲線(xiàn)點(diǎn)上有正確的切線(xiàn)方向了,但是只有出切線(xiàn),入切線(xiàn)直接把出切線(xiàn)反向就可以了,所以定義一個(gè)入切線(xiàn)的變量,把出切線(xiàn)的數(shù)組全部映射成負(fù)的傳進(jìn)來(lái)
function flipTan(p) {
? return -p;
}
var inTans = outTans.map(flipTan);
使用這個(gè)map()方法可以把一個(gè)數(shù)組的所有元素全部用括號(hào)中的函數(shù)計(jì)算一遍輸出出來(lái),使用方法就是定義一個(gè)函數(shù),把這個(gè)函數(shù)名放到map()中作為屬性,輸出出來(lái)的就是經(jīng)過(guò)函數(shù)計(jì)算過(guò)的數(shù)組了,它還有一種寫(xiě)法是這樣的
var inTans = outTans.map(p => {
return -p;
});
關(guān)于這個(gè)p => {}是javascript的箭頭函數(shù),它可以在括號(hào)內(nèi)部直接寫(xiě)函數(shù)而不用在外部定義,如果是一個(gè)比較簡(jiǎn)單的算法可以直接用下面這種寫(xiě)法
寫(xiě)好之后把入切線(xiàn)的數(shù)組給到createPath的inTangents屬性,再來(lái)調(diào)節(jié)下切線(xiàn)的長(zhǎng)度來(lái)看看

現(xiàn)在我們通過(guò)很少的點(diǎn)就生成了一條足夠平滑的正弦函數(shù)(不要把切線(xiàn)點(diǎn)當(dāng)成曲線(xiàn)點(diǎn)了哦,實(shí)際上我們的點(diǎn)只有12個(gè))
要說(shuō)明的是貝塞爾曲線(xiàn)無(wú)法完全精確的還原函數(shù)曲線(xiàn)的真實(shí)值,只能通過(guò)調(diào)節(jié)點(diǎn)之間的插值來(lái)擬合,比如圓形這種簡(jiǎn)單的圖形用貝塞爾曲線(xiàn)就是無(wú)法完全擬合的,即使我們?cè)谲浖锌吹降膱A已經(jīng)非常近似圓,但是它并不是真正的圓,有非常小的誤差值,當(dāng)然這個(gè)誤差值我們?nèi)庋蹘缀醪豢梢?jiàn)
所以在制作程序化曲線(xiàn)的時(shí)候我們也只能盡量擬合原曲線(xiàn),在點(diǎn)數(shù)和精確度之間找到一個(gè)平衡來(lái)用最少的點(diǎn)描述盡量精確的線(xiàn)
由于正弦函數(shù)的特點(diǎn),我們可以精確的把點(diǎn)放到它的峰值、峰谷、“山腰”上,也就是讓sin的取值點(diǎn)總是π/2的倍數(shù),具體寫(xiě)法如下
function sinCurve(s, a, t) {
var x = Math.PI * t;
var y = Math.sin(Math.PI * a * t);
return [x, y] * s;
}
function dSin(s, a, t) {
dy = Math.PI * a * Math.cos(Math.PI * a * t);
return [Math.PI, dy] * s;
}
var size = effect("size")(1);
var a = effect("a")(1);
var tanLen = effect("Tangents length")(1) / a;
var numpt = 4 * a + 1;
var CVS = new Array();
for (i = 0; i < numpt; i++) {
var t = -1 + i / (2 * a);
CVS.push(sinCurve(size, a, t));
}
var outTans = new Array();
for (i = 0; i < numpt; i++) {
var t = -1 + i / (2 * a);
outTans.push(dSin(tanLen, a, t));
}
var inTans = outTans.map(p => {
return -p;
});
createPath(points = CVS, inTangents = inTans, outTangents = outTans, is_closed = 0);

我們?cè)趕in中安排了一個(gè)系數(shù)a來(lái)控制曲線(xiàn)有多少個(gè)正弦循環(huán),并且把切線(xiàn)的長(zhǎng)度除以a,這樣在a增加的時(shí)候切線(xiàn)長(zhǎng)度會(huì)相應(yīng)的減少來(lái)適配調(diào)節(jié)后的曲線(xiàn),并且在a為0.5的倍數(shù)的時(shí)候,曲線(xiàn)的點(diǎn)會(huì)精確的落在正弦曲線(xiàn)在π/2的取值點(diǎn)上
然后我們可以來(lái)測(cè)試一下其他的曲線(xiàn),比如上面那個(gè)阿基米德螺旋線(xiàn),我們用了240個(gè)點(diǎn)才生成了近似平滑的形狀

現(xiàn)在我們添加導(dǎo)數(shù)公式
function curve(alpha, beta, t) {
var x = (alpha + beta * t) * Math.cos(t);
var y = (alpha + beta * t) * Math.sin(t);
return [x, y];
}
function dCurve(alpha, beta, t) {
var dx = beta * Math.cos(t) - (alpha + beta * t) * Math.sin(t);
var dy = beta * Math.sin(t) + (alpha + beta * t) * Math.cos(t);
return [dx, dy];
}
var Numpt = effect("number of point")("Slider");
var a = effect("alpha")(1);
var b = effect("beta")(1);
var tanLen = 10 * effect("Tangents Length")(1) / b / Numpt;
var step = effect("Endt")(1) / Numpt * 0.2 / Math.PI;
var CVS = new Array();
for (i = 0; i < Numpt; i++) {
var P = curve(a, b, i * step);
CVS.push(P);
}
var outTans = new Array();
for (i = 0; i < Numpt; i++) {
var P = dCurve(a, b, i * step) * tanLen;
outTans.push(P);
}
var inTans = outTans.map(p => {
return -p;
});
var Closed = effect("Closed")(1);
createPath(points = CVS, inTangents = inTans, outTangents = outTans, is_closed = Closed);

這次同樣的大小與螺旋圈數(shù)的情況下(實(shí)際上由于是開(kāi)放曲線(xiàn)所以稍微少了一段,因?yàn)檫@個(gè)表達(dá)式本來(lái)是基于閉合曲線(xiàn)改的,首尾不相連的情況下會(huì)少一個(gè)點(diǎn),但是這不重要),我們只用了16個(gè)點(diǎn)就生成了這個(gè)曲線(xiàn)
我把兩條曲線(xiàn)同時(shí)顯示出來(lái),看一下擬合程度


我把不帶切線(xiàn)的螺旋線(xiàn)隱藏起來(lái)再選中它的圖層,這樣在視圖中只顯示它的點(diǎn)
白線(xiàn)是用切線(xiàn)生成的螺旋線(xiàn),放大之后是可以看到有輕微的偏差的,這個(gè)偏差大小可以通過(guò)控制點(diǎn)數(shù)和切線(xiàn)的大小來(lái)調(diào)節(jié),使它盡量逼近原曲線(xiàn)
我就不在繼續(xù)深入研究如何獲得最大的擬合度了,一來(lái)是網(wǎng)上有很多計(jì)算獲得最大擬合度的方法的文章,二來(lái)做個(gè)動(dòng)畫(huà)沒(méi)必要搞得如此精確,三來(lái)我是真滴不會(huì)了

然后我們可以把那個(gè)心形的曲線(xiàn)計(jì)算一下切線(xiàn),它的切線(xiàn)公式是這樣的

導(dǎo)數(shù)這種東西當(dāng)然是早就忘了,復(fù)合函數(shù)導(dǎo)數(shù)算法我還是現(xiàn)查的,不過(guò)現(xiàn)在有很多可以符號(hào)計(jì)算的東西幫助我們算導(dǎo)數(shù),比如python有個(gè)sympy庫(kù)就可以直接用,但要是不太復(fù)雜的話(huà)就手算一下也可以,要是實(shí)在不會(huì)算的可以去找算卦的師傅算

關(guān)于這個(gè)導(dǎo)數(shù)要注意的是有小數(shù)次冪的情況下它是不能算出來(lái)負(fù)數(shù)區(qū)間的導(dǎo)數(shù)的,但是從圖像上很容易就看出來(lái)x^(2/3)這段曲線(xiàn)的負(fù)數(shù)部分就是正數(shù)部分的鏡像,導(dǎo)數(shù)也是一樣的鏡像,但是由于它的導(dǎo)數(shù)是(2/3)*x^(-1/3),表達(dá)式不認(rèn)這個(gè)函數(shù)有負(fù)區(qū)間

所以在寫(xiě)這個(gè)導(dǎo)數(shù)的時(shí)候要做一個(gè)判斷,讓x<0的時(shí)候x^(2/3)的導(dǎo)數(shù)為-(2/3)*(-x)^(-1/3),意思就是讓它計(jì)算對(duì)應(yīng)正區(qū)間的導(dǎo)數(shù),然后把得到的數(shù)值乘以負(fù)數(shù)來(lái)得到正確的負(fù)區(qū)間的導(dǎo)數(shù),另外再加一個(gè)當(dāng)x=0時(shí)候的情況,單獨(dú)設(shè)置這個(gè)點(diǎn)的導(dǎo)數(shù)為0
然后這個(gè)函數(shù)的導(dǎo)函數(shù)就寫(xiě)成了這個(gè)樣子
function dCurve(s, a, t) {
var x = t;
if (x > 0.000001) {
var y = (2 / 3) * Math.pow(x, (-1 / 3)) + 0.9 * Math.PI * a * Math.sqrt(8 - x ** 2) * Math.cos(Math.PI * a * x) - 0.9 * x * Math.pow((8 - x ** 2), -0.5) * Math.sin(Math.PI * a * x);
return [1, -y] * s * Math.pow(length([x, y]), -1);
} else if (x < -0.000001) {
var y = -(2 / 3) * Math.pow(-x, (-1 / 3)) + 0.9 * Math.PI * a * Math.sqrt(8 - x ** 2) * Math.cos(Math.PI * a * x) - 0.9 * x * Math.pow((8 - x ** 2), -0.5) * Math.sin(Math.PI * a * x); //Horizontal mirroring the positive derivative curve
return [1, -y] * s * Math.pow(length([x, y]), -1);
} else {
return [0, 0]; //If x is 0
}
}
寫(xiě)好之后最終這個(gè)函數(shù)被改成這樣了,其實(shí)很多是我加的注釋

看起來(lái)好像很復(fù)雜,其實(shí)還好啦,為了防止浮點(diǎn)誤差,我把判斷x是否大于小于0的判斷寫(xiě)成了與±0.000001進(jìn)行比較

//Controllable parameters
var size = effect("size")(1); //Total size
var n = effect("a")(1); //coefficient a
var tanLeng = effect("Tangents length")(1) / n; //The Length of tangents decrease as a increases
//Main function of curve
function curve(s, a, t) {
var x = t;
var y = Math.pow(Math.pow(x, 2), (1 / 3)) + 0.9 * Math.sqrt(8 - Math.pow(x, 2)) * Math.sin(Math.PI * a * x);
return [x, -y] * s;
}
//Derivative of the curve, need to determin the interval if the x is a negative number or 0
function dCurve(s, a, t) {
var x = t;
if (x > 0.000001) {
var y = (2 / 3) * Math.pow(x, (-1 / 3)) + 0.9 * Math.PI * a * Math.sqrt(8 - x ** 2) * Math.cos(Math.PI * a * x) - 0.9 * x * Math.pow((8 - x ** 2), -0.5) * Math.sin(Math.PI * a * x);
return [1, -y] * s * Math.pow(length([x, y]), -1);
} else if (x < -0.000001) {
var y = -(2 / 3) * Math.pow(-x, (-1 / 3)) + 0.9 * Math.PI * a * Math.sqrt(8 - x ** 2) * Math.cos(Math.PI * a * x) - 0.9 * x * Math.pow((8 - x ** 2), -0.5) * Math.sin(Math.PI * a * x); //Horizontal mirroring the positive derivative curve
return [1, -y] * s * Math.pow(length([x, y]), -1);
} else {
return [0, 0]; //If x is 0
}
}
//Derive parameters
var step1 = 1 / (2 * n); //x step with equal spacing
var step2 = (2 * Math.SQRT2 - 2) % step1; //x step in both sides
var numptConsOneSide = n * 4 + Math.floor((2 * Math.SQRT2 - 2) / step1); // The count of points with equal spacing in on side
var numpt = numptConsOneSide * 2 + 3; //tatal count of points
var posiPT = new Array();
for (i = 1; i <= numptConsOneSide; i++) {
posiPT.push(i * step1);
}
var negaPT = posiPT.map(p => {
return -p;
}).reverse();
var pt = [-(2 * Math.SQRT2 - 0.00000001)].concat(negaPT, [0], posiPT, [(2 * Math.SQRT2 - 0.00000001)]);
var CVS = new Array();
for (i = 0; i < numpt; i++) {
var P = curve(size, n, pt[i]);
CVS.push(P);
}
var outTans = new Array();
for (i = 0; i < numpt; i++) {
var tan = dCurve(tanLeng, n, pt[i]) * linear(Math.pow(-1, i + 1), -1, 1, 4, 1);
outTans.push(tan);
}
var inTans = outTans.map(p => {
return -p;
});
var Closed = effect("Closed")(1);
createPath(points = CVS, inTangents = inTans, outTangents = outTans, is_closed = Closed);
但這個(gè)切線(xiàn)在某些地方算出來(lái)的導(dǎo)數(shù)數(shù)值過(guò)大,所以我把最終的切線(xiàn)又做了一個(gè)映射讓太長(zhǎng)的切線(xiàn)自動(dòng)收成短的,雖然最終效果還是不太理想,但是勉強(qiáng)過(guò)得去吧
其他正常的參數(shù)方程我測(cè)試之后的效果都還可以


最后我們來(lái)解決齒輪的嚙合問(wèn)題
首先我們要知道,齒輪是一種工業(yè)零件,有很?chē)?yán)格的參數(shù)要求,如果你想做一個(gè)正常的齒輪嚙合動(dòng)畫(huà),像這種圖片里的簡(jiǎn)單齒輪是肯定不行的,它只不過(guò)是“看起來(lái)像齒輪”的東西

真正的齒輪為了保證傳動(dòng)穩(wěn)定,傳動(dòng)效率高,有模數(shù)、壓力角等一系列標(biāo)準(zhǔn)參數(shù),只有滿(mǎn)足參數(shù)的齒輪才能正確的嚙合在一起,我找了一張網(wǎng)上的嚙合原理動(dòng)圖

標(biāo)準(zhǔn)齒輪的嚙合曲面是個(gè)漸開(kāi)線(xiàn),嚙合過(guò)程中接觸位置的運(yùn)動(dòng)方向和面法線(xiàn)方向要保持一個(gè)穩(wěn)定的角度......
寫(xiě)到這里的時(shí)候我看了一下右下角已輸入字?jǐn)?shù)

我決定還是直接貼代碼結(jié)束這篇又臭又長(zhǎng)的文章吧

首先建立這幾個(gè)控制選項(xiàng)

然后建立空path,在其中輸入一下表達(dá)式
/*
Standard gear parameters:
z = 17~40, α = 20°, in some occasions, α=14.5°, 15°, 22.50° and 25°
*/
//Basic parameter m, z, alpha
var m = effect("Module m")(1); //Module
var z = Math.round(effect("Teeth z")(1)); //Number of teeth
var alpha = effect("Pressure Angle alpha")(1); //The pressure angle
//Derived parameter d, da, df, db
var d = m * z; //Pitch diameter
var da = m * (z + 2); //Tip circle diameter
var df = m * (z - 2.5); //Root circle diameter
var db = d * Math.cos(degreesToRadians(alpha)); //Base circle diameter
//Involute Formula
function Involute(r, theta) {
var x = r * Math.cos(theta) + (theta) * r * Math.sin(theta);
var y = r * Math.sin(theta) - (theta) * r * Math.cos(theta);
return [x, y];
}
//The derivative of involute
function dInvolute(r, theta) {
var dx = theta * Math.cos(theta);
var dy = theta * Math.sin(theta);
return [dx, dy];
}
//Calculate intersection of involute and circle
function calInter(j, r) {
var t = Math.sqrt(Math.pow((r / j), 2) - 1);
return t;
}
//Point on base circle
var pointBC = [db * 0.5, 0];
//Point on tip circle
var pointTC = Involute(db * 0.5, calInter(db * 0.5, da * 0.5));
//Point interpolation in the BC and TC
var pointHalfTC = Involute(db * 0.5, 0.7 * calInter(db * 0.5, da * 0.5));
//Point on pitch circle
var pointPC = Involute(db * 0.5, calInter(db * 0.5, d * 0.5));
//The function calculate rotated point
function rotateP(P, theta) {
var x = P[0];
var y = P[1];
var x0 = x * Math.cos(theta) - y * Math.sin(theta);
var y0 = x * Math.sin(theta) + y * Math.cos(theta);
return [x0, y0];
}
/*Rotate pointPC by π divide z*2,
and Get symmetry axis point*/
mirrorP = rotateP(pointPC, Math.PI / (z * 2));
//Symmetrical point of point A about a straight line passing through two points L1, L2
function symmetryP(P1, L1, L2) {
var x = P1[0];
var y = P1[1];
var A = L2[1] - L1[1];
var B = L1[0] - L2[0];
var C = -1 * A * L1[0] - B * L1[1];
var x0 = x - 2 * A * ((A * x + B * y + C) / (Math.pow(A, 2) + Math.pow(B, 2)));
var y0 = y - 2 * B * ((A * x + B * y + C) / (Math.pow(A, 2) + Math.pow(B, 2)));
return [x0, y0];
}
//Point on root circle, and round point
var pointRC = pointBC * df / db;
var Rround = clamp(effect("Roof Round")(1), 0, 80) * 0.01;
var roundAngle = (db - df) * Rround / df;
var pointRCV = pointRC * ((db - df) * Rround + df) / df;
var pointRCU = rotateP(pointRC, -roundAngle);
//Teeth point Array, mirror all points and copy around origin
var halfUnitPointsSet = [pointRCU, pointRCV, pointBC, pointHalfTC, pointTC];
var unitPointsSet = halfUnitPointsSet.concat(halfUnitPointsSet.map(p => {
return symmetryP(p, [0, 0], mirrorP);
}).reverse());
var totalPointsSet = new Array();
for (var i = 0; i < z; i++) {
totalPointsSet = totalPointsSet.concat(unitPointsSet.map(p => {
return rotateP(p, i * 2 * Math.PI / z);
}));
}
//Set tangents
function TanOfCP(cp) {
return normalize([cp[1], -cp[0]]);
}
var magicNumber = (4 / 3) * (Math.SQRT2 - 1);
var inTanRCV = [-(db - df) * Rround * 0.5 * magicNumber, 0];
var outTanRCV = [linear(Rround, 0, 1, (db - df) * 0.1, 0), 0];
var inTanRCU = TanOfCP(pointRCU) * length(outTanRCV);
var outTanRCU = -TanOfCP(pointRCU) * length(inTanRCV);
var inTanBC = [-outTanRCV[0], 0];
var outTanBC = [m * 0.2, 0];
var inTanHalfTC = -dInvolute(db * 0.5, 0.7 * calInter(db * 0.5, da * 0.5)) * m * 0.7;
var outTanHalfTC = -inTanHalfTC * 1.25;
var inTanTC = -dInvolute(da * 0.5, calInter(db * 0.5, da * 0.5)) * m * 0.5;
var outTanTC = -TanOfCP(pointTC) * m * 0.2;
//Tangents Array
var halfUnitIntangentsSet = [inTanRCU, inTanRCV, inTanBC, inTanHalfTC, inTanTC];
var halfUnitOuttangentsSet = [outTanRCU, outTanRCV, outTanBC, outTanHalfTC, outTanTC];
var unitInTangentsSet = halfUnitIntangentsSet.concat(halfUnitOuttangentsSet.map(p => {
return symmetryP(p, [0, 0], mirrorP);
}).reverse());
var unitOutTangentsSet = halfUnitOuttangentsSet.concat(halfUnitIntangentsSet.map(p => {
return symmetryP(p, [0, 0], mirrorP);
}).reverse());
var totalInTangentsSet = new Array();
for (var i = 0; i < z; i++) {
totalInTangentsSet = totalInTangentsSet.concat(unitInTangentsSet.map(p => {
return rotateP(p, i * 2 * Math.PI / z);
}));
}
var totalOutTangentsSet = new Array();
for (var i = 0; i < z; i++) {
totalOutTangentsSet = totalOutTangentsSet.concat(unitOutTangentsSet.map(p => {
return rotateP(p, i * 2 * Math.PI / z);
}));
}
//Create gear spline
createPath(points = totalPointsSet, inTangents = totalInTangentsSet, outTangents = totalOutTangentsSet, is_closed = true);

中間的小圓是后添加的,跟表達(dá)式無(wú)關(guān)
Module m是模數(shù)m,控制齒寬,直觀上直接控制了齒輪的整體縮放,
Teeth z是齒數(shù),
Pressure Angle就是壓力角,直觀來(lái)看的話(huà)是控制嚙合曲面的傾斜度,
RoofRound是控制齒根的圓滑程度,影響不大,只是控制根部過(guò)渡線(xiàn)而已,不參與嚙合
齒輪要嚙合的條件是模數(shù)與壓力角a要相等,百度百科中是這樣寫(xiě)的

標(biāo)準(zhǔn)齒輪的壓力角一般為20度,這種情況下齒數(shù)控制在17~40個(gè)是最好的,如果有特殊需求需要增加更多的齒數(shù)的話(huà),需要適量得減小壓力角

然后我們來(lái)生成兩個(gè)不同齒數(shù)的齒輪,一個(gè)齒數(shù)為30,一個(gè)齒數(shù)為20,將他們兩個(gè)的距離調(diào)成500(因?yàn)閮蓚€(gè)齒輪計(jì)算出來(lái)的分度圓直徑分別是600、400,所以需要相距500),然后把齒數(shù)為20的齒輪稍微旋轉(zhuǎn)一點(diǎn)角度讓他們?cè)陟o態(tài)上先嚙合上

兩個(gè)齒輪的傳動(dòng)比就是齒數(shù)之比,也就是齒數(shù)30的齒輪轉(zhuǎn)20圈,齒數(shù)20的齒輪就轉(zhuǎn)30圈,那么我們?cè)邶X數(shù)20的齒輪上引用齒數(shù)30的齒輪的旋轉(zhuǎn)值乘以-3/2(因?yàn)樾D(zhuǎn)方向要相反),并且加上剛才我們旋轉(zhuǎn)之后的那個(gè)偏移量
var rot = -thisComp.layer("Cogwheel").transform.rotation * 3 / 2;
rot + thisProperty.value
然后對(duì)另一個(gè)齒輪K旋轉(zhuǎn)動(dòng)畫(huà),或者直接輸入time表達(dá)式,看一下它們是否完美的嚙合在了一起

雖然我一直是用形狀層在生成這些曲線(xiàn),但是這些其實(shí)都只是路徑而已,也就是說(shuō)把他們放到mask里也同樣適用,只不過(guò)mask根據(jù)其依附的圖層類(lèi)型不同可能中心點(diǎn)不一樣,比如固態(tài)層中心點(diǎn)不是[0,0]
我們可以把這些效果粘貼到一個(gè)mask中作為E3D的自定義形狀

要注意在E3D中給了齒輪倒角的話(huà)要用expand edges這個(gè)屬性把倒角造成的邊緣擴(kuò)展再收回來(lái),不然齒寬會(huì)加厚

由于AE中表達(dá)式的執(zhí)行效率不是很高,這個(gè)齒輪其實(shí)生成出來(lái)之后就有點(diǎn)卡了,如果不需要對(duì)齒輪里的一些參數(shù)做動(dòng)畫(huà),可以把這個(gè)齒輪變成非參數(shù)曲線(xiàn),就好像自己畫(huà)出來(lái)的一樣,如果直接禁用表達(dá)式的話(huà),它無(wú)法保留這個(gè)表達(dá)式的輸出

那么可以用這個(gè)腳本來(lái)把表達(dá)式生成的屬性坍縮到屬性本身上,并且會(huì)自動(dòng)禁用表達(dá)式,拷貝以下代碼
var?selComp?=?app.project.activeItem;
var?selPro?=?selComp.selectedProperties;
function?collapsePro(Pro)?{
????if(Pro?instanceof?Property)?{
????????if?(Pro.numKeys?!=?0?||?Pro.expressionEnabled?==?1)?{
????????????try?{
????????????????var?vl?=?Pro.value;
????????????????var?nKeys?=?Pro.numKeys;
????????????????for?(j?=?0;?j?<?nKeys;?j++)?{
????????????????????Pro.removeKey(1);
????????????????}??//Delete?all?keyframes
????????????????Pro.expressionEnabled?=?0;
????????????????Pro.setValue(vl);
????????????}?catch(err)?{
????????????????alert(Pro.name?+?"?can?not?collapse");
????????????}
????????}?else?{
????????????;
????????}
????}?else?{
????????;
????}
}
app.beginUndoGroup("Collapse?Properties");
for?(i?=?0;?i<selPro.length;?i++)
{
??collapsePro(selPro[i]);
}
復(fù)制到一個(gè)文本文件,保存之后把文本文件的名字設(shè)置一下,并且后綴改成jsx,然后把這個(gè)文件放到AE的腳本目錄中

重啟AE,選中我們需要轉(zhuǎn)化的帶有表達(dá)式的屬性

在文件-腳本中找到我們剛才設(shè)置的腳本,也可以直接點(diǎn)擊run script file去文件夾中找到你要運(yùn)行的腳本


OK,表達(dá)式禁用了,而我們的齒輪形狀保留下來(lái)了,現(xiàn)在就不卡了
(如果你需要坍縮生成了動(dòng)畫(huà)的表達(dá)式,請(qǐng)使用這個(gè)功能

但是我們的旋轉(zhuǎn)動(dòng)畫(huà)不是齒輪的path動(dòng)畫(huà),path是靜態(tài)的,所以不需要這樣轉(zhuǎn)化關(guān)鍵幀)
然后如何計(jì)算這個(gè)齒輪,中間過(guò)程確實(shí)比較麻煩,我做了好多輔助線(xiàn)來(lái)計(jì)算漸開(kāi)線(xiàn),仿佛回到了大學(xué)對(duì)著一張圖一畫(huà)一整天的時(shí)代

所以這篇文章里就不講解具體怎么做了,反正也沒(méi)人學(xué),學(xué)了也沒(méi)有用,真有用的話(huà)直接拿來(lái)用不就好了,再不行把C4D里的齒輪曲線(xiàn)導(dǎo)出一下也行
但如果真滴有人能看完這三篇文章的話(huà)就算什么都沒(méi)學(xué)到起碼也獲得了一個(gè)生成工業(yè)齒輪的方法(強(qiáng)行解說(shuō)存在價(jià)值)

當(dāng)然為了防止有那么無(wú)聊的人真的想學(xué),之后我就單開(kāi)一個(gè)專(zhuān)欄貼解釋一下計(jì)算過(guò)程吧

后記:自從寫(xiě)完上一篇貼之后楞是忙到一直沒(méi)空寫(xiě)內(nèi)容,但是只要一有點(diǎn)瑣碎的時(shí)間就會(huì)找算法,找代碼寫(xiě)法,哪怕是半夜等渲染空出來(lái)的一點(diǎn)點(diǎn)時(shí)間
其實(shí)我也懷疑這些東西到底有沒(méi)有用,一輩子能不能有機(jī)會(huì)用上,但是看了畢導(dǎo)那個(gè)講化學(xué)家有多無(wú)聊的視頻之后我也就釋然了,可能我無(wú)需思考這些問(wèn)題,這是我想研究的內(nèi)容,所以我就去研究了,是興趣使然,而不是為了用在xxx上所以去找方法,大部分的實(shí)際工作都如擰螺絲一般無(wú)聊枯燥,我也完全可以做一個(gè)無(wú)情的套模板機(jī)器,但是當(dāng)你的技能需要的知識(shí)水平遠(yuǎn)遠(yuǎn)超出工作的需求而你還能繼續(xù)向著更深層次鉆研的話(huà),可能你就是真的喜歡那件事情
當(dāng)我花了很多時(shí)間把之前的某個(gè)想法實(shí)現(xiàn)之后,也遠(yuǎn)比結(jié)束了一個(gè)無(wú)聊的項(xiàng)目要開(kāi)心,當(dāng)然能結(jié)掉一個(gè)項(xiàng)目也很高興啦,但是這兩種心情好比一個(gè)是獲得了新禮物的喜悅,一個(gè)是終于放下了負(fù)擔(dān)的喜悅
其實(shí)人們或多或少在人生的某個(gè)階段相信過(guò)讀書(shū)無(wú)用論,覺(jué)得學(xué)校這些知識(shí)在社會(huì)上毫無(wú)用處,這種想法隨著閱歷增加都會(huì)漸漸摒棄掉,曾經(jīng)我也在懷疑高中學(xué)的東西除了為了高考拿個(gè)分?jǐn)?shù)是否還有用,疑惑浪費(fèi)四年在大學(xué)學(xué)知識(shí)但最終從事了一個(gè)不相干的工作,那大學(xué)四年的時(shí)光對(duì)我又有什么意義,但是隨著我想要實(shí)現(xiàn)的效果難道越來(lái)越難,需要的知識(shí)越來(lái)越多的時(shí)候,曾經(jīng)覺(jué)得完全用不到的知識(shí)竟然派上了用場(chǎng),應(yīng)了那句“書(shū)到用時(shí)方恨少”。
某種意義上,這就是一種廢物利用吧,高數(shù)微積分對(duì)我來(lái)說(shuō)本是廢物,但現(xiàn)在我能利用上它們的時(shí)候,就不是廢物了
可惜我自己還是個(gè)廢物,啥時(shí)候我能被利用上啊


而且我發(fā)現(xiàn)沒(méi)時(shí)間學(xué)習(xí)的時(shí)候反而注重抓住一些瑣碎的時(shí)間來(lái)學(xué)習(xí),好幾天都有時(shí)間反而懶惰了
總之通過(guò)這段時(shí)間的學(xué)習(xí)突然意識(shí)到以前學(xué)過(guò)的東西真滴能用上,就很開(kāi)心了,即使現(xiàn)在看起來(lái)不堪大用,未來(lái)也說(shuō)不定會(huì)成為某個(gè)想法的實(shí)現(xiàn)基礎(chǔ)
哦,以上這些不在正文,可以不用看
不知不覺(jué)這個(gè)文章快寫(xiě)了2w字了,怎么高中寫(xiě)作文就憋不出這么多屁來(lái)
