private void FadeIn(float timeFromBirth)
    {
        // fadeDelta 변수 - 페이드아웃이 몇 % 진행되었는지 델타값, SetVertsPos()에서도 사용하기때문에 private 처리.
        fadeDelta = timeFromBirth / appearTime;

        //Color newColor = Color.Lerp(Color.white, soxLtn.fadeOutColor, delta);
        switch (appearColorType)
        {
        case SoxLtn.AnimCurveType.CONSTANT:
            break;

        case SoxLtn.AnimCurveType.LINEAR:
            ColorStreak(appearColor, mainColor, fadeDelta);
            break;

        case SoxLtn.AnimCurveType.EASEIN:
            ColorStreak(appearColor, mainColor, SoxLtn.EaseIn(0f, 1f, fadeDelta));
            break;

        case SoxLtn.AnimCurveType.EASEOUT:
            ColorStreak(appearColor, mainColor, SoxLtn.EaseOut(0f, 1f, fadeDelta));
            break;

        default:
            break;
        }
    }
    private static void CreateLtn(MenuCommand sel)
    {
        GameObject newLtn = new GameObject("LightningEmitter");
        SoxLtn     soxLtn = newLtn.AddComponent <SoxLtn>();

        SoxLtnUtilEditor.CheckNodes(soxLtn);
        soxLtn.version = soxLtn.versionNow;

        GameObject selObj = (GameObject)sel.context;

        if (selObj != null)
        {
            newLtn.transform.SetParent(selObj.transform);
            newLtn.transform.localPosition    = Vector3.zero;
            newLtn.transform.localEulerAngles = Vector3.zero;
            newLtn.transform.localScale       = Vector3.one;
        }

        if (Selection.gameObjects.Length <= 1)
        {
            Selection.activeGameObject = newLtn;
        }

        Undo.RegisterCreatedObjectUndo(newLtn, "Create Lightning Emitter");
    }
    private void FadeOut(float timeFromBirth)
    {
        // fadeDelta 변수 - 페이드아웃이 몇 % 진행되었는지 델타값, SetVertsPos()에서도 사용하기때문에 private 처리.
        fadeDelta = 1f - (timeFromBirth - fadeTimeRest) / fadeTime;

        // 다이나믹 타입은 SetVertsPos() 에서 폭을 조절한다.
        if (animType == SoxLtn.AnimType.STATIC)
        {
            switch (fadeOutThinType)
            {
            case SoxLtn.AnimCurveType.CONSTANT:
                break;

            case SoxLtn.AnimCurveType.LINEAR:
                ThinStreak(fadeDelta);
                break;

            case SoxLtn.AnimCurveType.EASEIN:
                ThinStreak(SoxLtn.EaseIn(0f, 1f, fadeDelta));
                break;

            case SoxLtn.AnimCurveType.EASEOUT:
                ThinStreak(SoxLtn.EaseOut(0f, 1f, fadeDelta));
                break;

            default:
                break;
            }
        }

        switch (fadeOutColorType)
        {
        case SoxLtn.AnimCurveType.CONSTANT:
            if (fadeState != fadeStateBefore)
            {
                // CONSTANT 라도 한 번은 세팅해준다.
                ColorStreak(fadeOutColor, mainColor, fadeDelta);
                fadeStateBefore = fadeState;
            }
            break;

        case SoxLtn.AnimCurveType.LINEAR:
            ColorStreak(fadeOutColor, mainColor, fadeDelta);
            break;

        case SoxLtn.AnimCurveType.EASEIN:
            ColorStreak(fadeOutColor, mainColor, SoxLtn.EaseOut(0f, 1f, fadeDelta));
            break;

        case SoxLtn.AnimCurveType.EASEOUT:
            ColorStreak(fadeOutColor, mainColor, SoxLtn.EaseIn(0f, 1f, fadeDelta));
            break;

        default:
            break;
        }
    }
    // Position Offset Animation을 위해 lifeDelta, scaleVar, 보간 애니메이션 타입을 고려한 벡터를 리턴
    private Vector3 GetPosOffsetAnimPos()
    {
        Vector3 returnVec = Vector3.zero;

        switch (posOffsetAnimType)
        {
        case SoxLtn.AnimCurveType.EASEOUT:
            returnVec = posOffsetAnim * SoxLtn.EaseOut(0f, 1f, lifeDelta) * scaleVar;
            break;

        case SoxLtn.AnimCurveType.EASEIN:
            returnVec = posOffsetAnim * SoxLtn.EaseIn(0f, 1f, lifeDelta) * scaleVar;
            break;

        default:
            //CONSTANT, LINEAR
            returnVec = posOffsetAnim * lifeDelta * scaleVar;
            break;
        }

        return(returnVec);
    }
    // 버택스 위치 선정, 애니메이션 등이 모두 이 함수에서 수행된다.
    // 버택스 위치가 달라질 때 UV도 연동되려면 UV 값도 이곳에서 처리되어야 한다. 그러나 퍼포먼스를 위해 UV 연동은 생략
    public void SetVertsPos()
    {
        // soxLtn이 삭제되면 SetVertsPos() 작동을 멈춘다.
        // soxLtn이 삭제되어도 이펙트가 살아있도록 하려면 노드 트랜스폼에 대한 매트릭스 연산방식으로 로직을 변경해야하는데 현재는 노드 트랜스폼으로부터 직접 포인트 연산을 하는 방식이라서 이펙트가 살아있는 처리는 나중에...
        if (!soxLtn || !soxLtnNodes[0])
        {
            return;
        }
        // SetVertsPos() 함수는 DYNAMIC 타입에서 계속 불리기때문에 distance all을 계속 참조해야한다.
        nodeDistanceAll = soxLtn.nodeDistanceAll;

        // 스케일 애니메이션이 되는 경우도 있으므로 실시간으로 스케일값을 업데이트
        scaleVar = (
            Mathf.Abs(soxLtn.transform.lossyScale.x) +
            Mathf.Abs(soxLtn.transform.lossyScale.y) +
            Mathf.Abs(soxLtn.transform.lossyScale.z)
            ) / 3f;

        if (scaleVar == 0f)
        {
            // scaleVar 로 나누는 경우가 종종 있어여 0이 되는 상황을 강제로 막아준다.
            scaleVar = 1f;
        }

        // Position Offset Animation 을 사용할 경우 임시로 Node 위치를 이동시켜둔다. (soxLtn을 옮기면 자식도 한방에 옮길 수 있지만 여러 계층구조가 얽힌 라이트닝도 고려해야해서 node들을 하나씩 옮김)
        // for 루프가 끝날 때 다시 원위치 해줘야한다.
        Vector3 tempPosOffsetAnim = Vector3.one;

        if (ifUsePosOffsetAnim)
        {
            tempPosOffsetAnim = GetPosOffsetAnimPos();
            for (int i = 0; i < soxLtnNodes.Length; i++)
            {
                soxLtnNodes[i].transform.position += tempPosOffsetAnim;
            }
        }

        Matrix4x4 tempZRotMatrix  = Matrix4x4.identity;
        float     nodeDistanceSum = 0f;
        Vector3   waveVector;

        meMatrix        = meTrans.localToWorldMatrix;
        meMatrixInverse = meMatrix.inverse;

        // 노드 매트릭스를 미리 계산해둔다. (for 문 안에서 nodeMatrixs[i], nodeMatrixs[i -1] 등으로 쓰일 일이 있음)
        for (int i = 0; i < soxLtnNodes.Length; i++)
        {
            nodeMatrixs[i] = soxLtnNodes[i].transform.localToWorldMatrix;
        }

        for (int i = 0; i < soxLtnNodes.Length; i++)
        {   // i 카운트는 노드를 의미함
            // 노드 위치 버택스들 먼저 처리

            nodeVerts[i] = new Vector3(0, 0, 0);

            // Expand용 tDirVec 방향 벡터 세팅
            Vector3 tDirVec;
            if (soxLtnNodes[i].nodeType == SoxLtnNode.NodeType.CIRCLE)
            {
                tDirVec = expDirCircle;
            }
            else
            {
                tDirVec = expDirSphere;
            }

            // zTwists 만큼 회전 매트릭스 세팅 (tempRotMatrix) (zTwists 는 각 노드마다 비틀림 값을 지정할 수 있는 기능)
            tempZRotMatrix.SetTRS(Vector3.zero, Quaternion.Euler(0, 0, zTwists[i]), Vector3.one);

            tDirVec = tempZRotMatrix.MultiplyPoint3x4(tDirVec);

            // circle 혹은 shpere 방향으로 확장 벡터를 더해준다.
            if (soxLtnNodes[i].fillType == SoxLtnNode.FillType.INSIDE)
            {   // INSIDE
                nodeVerts[i] += tDirVec * expDist * soxLtnNodes[i].circleArea;
                // 확장 거리 변수에 시간 변화에 따른 누적 값 반영
                expDist += expandAnimSpd * anyDeltaTime;
            }
            else
            {   //SURFACE
                nodeVerts[i] += tDirVec * expDistSurf * soxLtnNodes[i].circleArea;
                // 확장 거리 변수에 시간 변화에 따른 누적 값 반영
                expDistSurf += expandAnimSpd * anyDeltaTime;
            }

            // halfWidth 이펙트 두께 설정 (두께의 절반)
            switch (fadeState)
            {
            case FadeState.FADEIN:
                switch (appearThinType)
                {
                case SoxLtn.AnimCurveType.CONSTANT:
                    if (fadeState != fadeStateBefore)
                    {
                        halfWidth       = new Vector3(-streakWidthHalf, 0, 0);
                        fadeStateBefore = fadeState;
                    }
                    break;

                case SoxLtn.AnimCurveType.LINEAR:
                    halfWidth = new Vector3(-streakWidthHalf * fadeDelta, 0, 0);
                    break;

                case SoxLtn.AnimCurveType.EASEIN:
                    halfWidth = new Vector3(-streakWidthHalf * SoxLtn.EaseIn(0f, 1f, fadeDelta), 0, 0);
                    break;

                case SoxLtn.AnimCurveType.EASEOUT:
                    halfWidth = new Vector3(-streakWidthHalf * SoxLtn.EaseOut(0f, 1f, fadeDelta), 0, 0);
                    break;

                default:
                    break;
                }
                break;

            case FadeState.MAIN:
                halfWidth = new Vector3(-streakWidthHalf, 0, 0);
                break;

            case FadeState.FADEOUT:
                switch (fadeOutThinType)
                {
                case SoxLtn.AnimCurveType.CONSTANT:
                    halfWidth = new Vector3(-streakWidthHalf, 0, 0);
                    break;

                case SoxLtn.AnimCurveType.LINEAR:
                    halfWidth = new Vector3(-streakWidthHalf * fadeDelta, 0, 0);
                    break;

                case SoxLtn.AnimCurveType.EASEIN:
                    halfWidth = new Vector3(-streakWidthHalf * SoxLtn.EaseOut(0f, 1f, fadeDelta), 0, 0);
                    break;

                case SoxLtn.AnimCurveType.EASEOUT:
                    halfWidth = new Vector3(-streakWidthHalf * SoxLtn.EaseIn(0f, 1f, fadeDelta), 0, 0);
                    break;

                default:
                    break;
                }
                break;
            }

            // 현재 노드까지의 누적 길이 합산
            nodeDistanceSum += soxLtnNodes[i].distance;

            // Cone 두께 halfWidthCone 설정
            float   tempCone = 1f;
            Vector3 halfWidthCone;
            if (ifUseCone)
            {
                tempCone      = GetCone(nodeDistanceSum);
                halfWidthCone = halfWidth * tempCone;
            }
            else
            {
                halfWidthCone = halfWidth;
            }

            int     nodeVertIndex = (subStep + 1) * (i * 2);
            Vector3 finalWorldPos1;
            Vector3 finalWorldPos2;
            if (ifUseWave && soxLtn.waves != null)
            {
                // Wave를 사용하는 경우
                // waveVector는 노드의 로컬방향이 반영되도록 하기 위해서 노드 트랜스폼을 기준으로 적용한다. (이후 서브 버택스의 웨이브는 매트릭스와 함께 연산)
                waveVector     = GetWave(nodeDistanceSum) * tempCone;
                finalWorldPos1 = nodeMatrixs[i].MultiplyPoint3x4(nodeVerts[i] + halfWidthCone + waveVector);
                finalWorldPos2 = nodeMatrixs[i].MultiplyPoint3x4(nodeVerts[i] - halfWidthCone + waveVector);
            }
            else
            {   // Wave를 사용하지 않는 경우
                finalWorldPos1 = nodeMatrixs[i].MultiplyPoint3x4(nodeVerts[i] + halfWidthCone);
                finalWorldPos2 = nodeMatrixs[i].MultiplyPoint3x4(nodeVerts[i] - halfWidthCone);
            }
            vertices[nodeVertIndex]     = meMatrixInverse.MultiplyPoint3x4(finalWorldPos1); //노드의 로컬을 월드로 변환한 뒤 월드를 이펙트의 로컬로 다시 변환
            vertices[nodeVertIndex + 1] = meMatrixInverse.MultiplyPoint3x4(finalWorldPos2); //노드의 로컬을 월드로 변환한 뒤 월드를 이펙트의 로컬로 다시 변환

            // PlayLinkedParticles()을 SetVertsPos()에서 해주는 이유는, 노드를 범위로 지정할 경우 라이트닝이 생겨날 위치 계산이 복잡해서 버택스 처리하면서 같이 처리해주기 위함
            if (Application.isPlaying && !ifPlayParticle)
            {
                // ifPlayParticle에 의해 딱 한번만 플레이 한다.
                PlayLinkedParticles(meMatrix.MultiplyPoint3x4((vertices[nodeVertIndex] + vertices[nodeVertIndex + 1]) * 0.5f), i);
            }

            // 여기부터 서브스텝 버택스들 처리
            if (i != 0)
            {   // 첫 번째 노드는 서브스텝이 없어서 i가 0일 때에는 처리하지 않는다
                // 서브스텝 버택스들 처리

                float tempNoiseType = 1f;
                if (subStepNoiseAmpType == SoxLtn.ConstantRelated.DISTANCE_RELATED)
                {
                    tempNoiseType = soxLtnNodes[i].distance;
                }

                for (int j = 1; j <= subStep; j++)
                {   // j 카운트는 서브스텝 버택스 순서를 의미함
                    // bias 변수는 두 노드 사이에 서브스텝에 의해 간격이 어느 위치에 있어야하는지 비율 값이다. 주로 Lerp 의 세 번째 매개변수로 사용
                    float bias = ((1f / (float)(subStep + 1)) * j);
                    // biasForWave? bias 값은 부드러운 서브스텝을 위해 중간에 변형되므로 wave의 distance 기준이 될 수 있는 순수한 bias 값이 필요함
                    float biasForWave = bias;

                    // shiftStart와 shiftEnd 값은 노드 로컬 방향을 기준으로 z 방향으로 이동한 서브스텝 포인트의 위치. 이 쉬프트 덕분에 substep이 부드러운 곡선을 그린다.
                    Vector3 shiftStart = new Vector3(0, 0, soxLtnNodes[i].distance * bias);
                    Vector3 shiftEnd   = new Vector3(0, 0, soxLtnNodes[i].distance * (1f - bias) * -1f);

                    // lossy 스케일 반영 (마이너스 스케일이 있을 수 있음) shiftStart와 shiftEnd는 z값만 존재
                    // distance 는 distance 값인데, 스케일이 커지면 역으로 작아져야한다. 그 이유는, TransformPoint (매트릭스) 연산은 동일한 입력 값에 대해서 로컬 스케일이 커지면 더 큰 값을 도출하기때문에 distance는 작아져야 같은 형상을 유지한다.
                    shiftStart.z /= soxLtnNodes[i].transform.lossyScale.z;
                    shiftEnd.z   /= soxLtnNodes[i].transform.lossyScale.z;

                    // !! 순서 중요
                    // 부드러운 곡선을 위해 bias에 약간의 특수 처리. shiftStart와 shiftEnd 값을 먼저 세팅한 뒤에 bias에 변화를 주는 순서가 중요함
                    if (i == 1 || i == (soxLtnNodes.Length - 1))
                    {   // 서브스텝이 시작이거나 끝 노드쪽일 경우
                        if (i == 1)
                        {
                            bias = SoxLtn.EaseOut(0f, 1f, bias);
                        }
                        if (i == (soxLtnNodes.Length - 1))
                        {
                            bias = SoxLtn.EaseIn(0f, 1f, bias);
                        }
                    }
                    else
                    {   // 서브스텝이 중간 노드쪽일 경우
                        if (bias < 0.5f)
                        {
                            bias -= ((0.5f - bias) * (bias / 0.5f));
                        }
                        else
                        {
                            bias += ((bias - 0.5f) * (1f - bias) / 0.5f);
                        }
                    }

                    int substepIndex      = (subStep + 1) * (i - 1) * 2 + (j * 2);
                    int substepNoiseIndex = (i - 1) * subStep + j - 1;

                    // shiftLerp 위치는 두께 halfWidth를 고려하지 않은 서브스텝 위치
                    Vector3 shiftLerp = Vector3.Lerp
                                            ( // 라이트닝 노드 기준으로 로컬 포지션을 월드 포지션으로 변환한 뒤 Lerp
                                            nodeMatrixs[i - 1].MultiplyPoint3x4(nodeVerts[i - 1] + shiftStart),
                                            nodeMatrixs[i].MultiplyPoint3x4(nodeVerts[i] + shiftEnd),
                                            bias
                                            );
                    Vector3 noise = subStepNoises[substepNoiseIndex] * subStepNoiseAmp * tempNoiseType * scaleVar;
                    // 서브스텝 위치의 zTwist 로테이션 매트릭스 (두께를 적용하려면 두께 벡터를 노드 방향으로 이동시켜야함, 노드는 카메라 룩앳)
                    tempZRotMatrix.SetTRS(Vector3.zero, Quaternion.Slerp(soxLtnNodes[i - 1].transform.rotation, soxLtnNodes[i].transform.rotation, bias), Vector3.one);

                    float subDistanceSum = 0f;
                    if (ifUseWave || ifUseCone)
                    {
                        // 서브스텝 버택스의 distance
                        subDistanceSum = nodeDistanceSum + soxLtnNodes[i].distance * biasForWave - soxLtnNodes[i].distance;
                    }

                    if (ifUseCone)
                    {
                        tempCone      = GetCone(subDistanceSum);
                        halfWidthCone = halfWidth * tempCone;
                    }
                    else
                    {
                        halfWidthCone = halfWidth;
                    }

                    // lossy 스케일 반영, 마이너스 스케일이 있을 수 있음
                    halfWidthCone.x *= soxLtnNodes[i].transform.lossyScale.x;

                    // 월드 포지션을 다시 meTrans 의 로컬 포지션으로 변환
                    if (ifUseWave && soxLtn.waves != null)
                    {
                        // waveVector는 노드의 로컬방향이 반영되도록 하기 위해서 매트릭스 연산 안에 넣어준다.
                        waveVector                 = GetWave(subDistanceSum) * tempCone;
                        vertices[substepIndex]     = meMatrixInverse.MultiplyPoint3x4(shiftLerp + tempZRotMatrix.MultiplyPoint3x4(halfWidthCone + waveVector)) + noise;
                        vertices[substepIndex + 1] = meMatrixInverse.MultiplyPoint3x4(shiftLerp + tempZRotMatrix.MultiplyPoint3x4(halfWidthCone * -1f + waveVector)) + noise;
                    }
                    else
                    {
                        vertices[substepIndex]     = meMatrixInverse.MultiplyPoint3x4(shiftLerp + tempZRotMatrix.MultiplyPoint3x4(halfWidthCone)) + noise;
                        vertices[substepIndex + 1] = meMatrixInverse.MultiplyPoint3x4(shiftLerp + tempZRotMatrix.MultiplyPoint3x4(halfWidthCone * -1f)) + noise;
                    }
                } // for j end
            }     // if end
        }         // for i end
        ifPlayParticle = true; // for문 밖에서 처리해야함.

        // Position Offset Animation 을 사용할 경우 임시로 Node 위치를 이동시켜두었던 것을
        // for 루프가 끝났으니 때 다시 원위치.
        if (ifUsePosOffsetAnim)
        {
            for (int i = 0; i < soxLtnNodes.Length; i++)
            {
                soxLtnNodes[i].transform.position -= tempPosOffsetAnim;
            }
        }

        mesh.vertices = vertices;

        mesh.RecalculateBounds();
        if (recalculateNormals)
        {
            mesh.RecalculateNormals();
        }
        if (recalculateTangents)
        {
            mesh.RecalculateTangents();
        }
    } // end of SetVertsPos()