void CreateBlock(Side hand, Heading heading, LiveNote note)
    {
//		print("create " + hand + " " + heading);
        LiveBlock block = null;

        var node = deadBlockList.First;

        while (node != null)
        {
            var next = node.Next;

            var deadBlock = node.Value;
            if (deadBlock.startTime + recycleInterval < MusicPlayer.LiveTime)                // Ready to be revived
            {
                block = deadBlock;
                deadBlockList.Remove(node);
                break;
            }

            node = next;
        }

        if (block == null)
        {
            node = inactiveBlockList.First;
            if (node != null)
            {
                block = node.Value;
                block.gameObject.SetActive(true);
                inactiveBlockList.RemoveFirst();
            }
        }

        if (block == null)
        {
            var go = Instantiate(blockPrototype, transform);
            block = go.GetComponent <LiveBlock>();
        }

        block.Init(hand);
        block.heading = heading;

        block.createTime = MusicPlayer.LiveTime;
        block.startTime  = note.startTime;
        block.isParallel = note.isPara;
        block.isLong     = note.isSpecial;

        block.startX = note.x;
        block.startY = note.y + 1;
        block.x      = note.x;
        block.y      = note.y;
        block.transform.Rotate(HeadingToRotation(heading), 0, 0);

        block.canvas.rotation = Quaternion.identity;
//		block.uiDirectionTrans.localEulerAngles = new Vector3(0, 0, -HeadingToRotation(heading));

        liveBlockList.AddLast(block);
        total += 1;
    }
    /**
     * tip  - lastTip
     *  |   \     |
     * base - lastBase
     */
    void DetectCollision(SwordTip tip, LiveBlock block)
    {
        Vector3 tipPosition = tip.tipPosition, lastTipPosition = tip.lastTipPosition, basePosition = tip.basePosition, lastBasePosition = tip.lastBasePosition;
//		Debug.DrawLine(tipPosition, lastTipPosition, Color.magenta);
//		Debug.DrawLine(tipPosition, lastBasePosition, Color.magenta);
//		Debug.DrawLine(tipPosition, basePosition, Color.magenta);
//		Debug.DrawLine(lastBasePosition, lastTipPosition, Color.magenta);
//		Debug.DrawLine(lastBasePosition, basePosition, Color.magenta);

        float   t;
        Vector3 blockTopLeft = block.transform.TransformPoint(blockPoint1), blockTipRight = block.transform.TransformPoint(blockPoint2);

        Debug.DrawLine(blockTopLeft, blockTipRight, Color.cyan);

        if (RayQuadIntersection(blockTopLeft, blockTipRight - blockTopLeft, tipPosition, lastTipPosition, lastBasePosition, basePosition, out t) && 0 <= t && t <= 1)
        {
            block.Collide(tip);
        }
    }
    void KillBlock(LiveBlock block)
    {
        double offset = Math.Abs(block.startTime - MusicPlayer.LiveTime);

        if (offset <= TimeGood)
        {
            combo += 1;
            if (combo > maxCombo)
            {
                maxCombo = combo;
            }
        }
        else
        {
            combo = 0;
        }

        float inc = block.isParallel ? BaseParaScore : BaseNormalScore;

        inc   *= GetScoreOffsetAddon(offset) * GetScoreComboAddon(combo);
        score += (int)inc;

        AudioFxConfig audioConfig;
        Material      material;
        string        text;

//		float scale;
        if (offset <= TimePerfect)
        {
//			scale = 2;
            material    = perfectMaterial;
            text        = perfectText;
            audioConfig = block.isLong ? specialAudioConfigs[Random.Range(0, specialAudioConfigs.Length)] : perfectAudioConfigs[Random.Range(0, perfectAudioConfigs.Length)];
        }
        else if (offset <= TimeGreat)
        {
//			scale = 1.5f;
            material    = greatMaterial;
            text        = greatText;
            audioConfig = greatAudioConfigs[Random.Range(0, greatAudioConfigs.Length)];
        }
        else if (offset <= TimeGood)
        {
//			scale = 1;
            material    = goodMaterial;
            text        = goodText;
            audioConfig = goodAudioConfigs[Random.Range(0, goodAudioConfigs.Length)];
        }
        else              // if (offset <= TimeBad)
//			scale = 0.5f;
        {
            material    = badMaterial;
            text        = badText;
            audioConfig = badAudioConfigs[Random.Range(0, badAudioConfigs.Length)];
        }

        var blockPosition = block.transform.position;

        hitParticleFxPreset.configs[0].count = combo << 1;          // 0th must be CubeCoin
        ParticleFxPool.Emit(hitParticleFxPreset, blockPosition, block.transform.rotation);
        FlashFxPool.Flash(hitFlashFxConfig, blockPosition);
        AudioFxPool.Play(audioConfig, blockPosition);

        uiScoreText.material    = material;
        uiIncText.material      = material;
        uiComboText.material    = material;
        uiScoreText.text        = score.ToString("N0");
        uiIncText.text          = "+" + inc.ToString("N0");
        uiComboText.text        = string.Format(combo == 0 ? "x {0:N0} x" : combo == maxCombo ? "^ {0:N0} ^" : "+ {0:N0} +", combo);
        uiScoreTextTween.isDead = false;
        uiScoreTextTween.time   = 0;
        uiComboTextTween.isDead = false;
        uiComboTextTween.time   = 0;
        uiIncTextTween.isDead   = false;
        uiIncTextTween.time     = 0;

        block.Die();

        block.uiComboText.material = material;
        block.uiJudgeText.material = material;
        block.uiScoreText.material = material;

        block.uiScoreText.text = string.Format("+{0:N0}", inc);
        block.uiJudgeText.text = text;
        if (combo > 10)
        {
            block.uiComboText.text = string.Format("{0:N0}", combo);
        }

        TweenManager.AddTween(new Tween(recycleInterval, textTween.easingType, textTween.easingPhase, step => {
            float inverse = (1f - step);            // * scale;
            var size      = new Vector3(inverse, inverse, inverse);
//			block.uiScoreText.color = new Color(1, 1, 1, inverse);
//			block.uiJudgeText.color = new Color(1, 1, 1, inverse);
//			block.uiComboText.color = new Color(1, 1, 1, inverse);
            block.uiScoreText.transform.localScale = size;
            block.uiJudgeText.transform.localScale = size;
            block.uiComboText.transform.localScale = size;
        }));
    }
    void UpdateBlock(LiveBlock block)
    {
//		const float damping = 100;

        if (!block.isClosed)
        {
            double closeTime = MusicPlayer.LiveTime - block.createTime;
            if (closeTime > closeDuration)
            {
                block.isClosed = true;
                ParticleFxPool.Emit(closeParticleFxPreset, block.transform.position, block.transform.rotation);
                FlashFxPool.Flash(closeFlashFxConfig, block.transform.position);

                block.leftShellTrans.localPosition  = Vector3.zero;
                block.rightShellTrans.localPosition = Vector3.zero;
            }
            else
            {
                block.leftShellTrans.localPosition  = new Vector3(0, 0, Easing.Ease(closeEasingType, closeEasingPhase, shellStart, -shellStart, closeTime, closeDuration));
                block.rightShellTrans.localPosition = new Vector3(0, 0, Easing.Ease(closeEasingType, closeEasingPhase, -shellStart, shellStart, closeTime, closeDuration));
            }
        }

        if (MusicPlayer.LiveTime <= block.startTime)            // Not yet
        {
            var offset = block.startTime - MusicPlayer.LiveTime;
            var x      = Easing.Ease(bufferEasingTypeX, bufferEasingPhaseX, block.x, bufferX * block.startX, offset, bufferInterval);
            var y      = Easing.Ease(bufferEasingTypeY, bufferEasingPhaseY, block.y, bufferY * block.startY, offset, bufferInterval);
            var z      = Easing.Ease(bufferEasingTypeZ, bufferEasingPhaseZ, 0, bufferZ, offset, bufferInterval);
//			block.transform.localPosition = Vector3.Lerp(block.transform.localPosition, new Vector3(x, y, z), UnityEngine.Time.deltaTime * damping);
            block.transform.localPosition = new Vector3(x, y, z);
        }
        else if (MusicPlayer.LiveTime - block.startTime < cacheInterval && MusicPlayer.LiveTime - block.startTime < TimeBad)              // Over due
        {
            var offset = MusicPlayer.LiveTime - block.startTime;
            var x      = Easing.Ease(cacheEasingTypeX, cacheEasingPhaseX, block.x, cacheX * block.x, offset, cacheInterval);
            var y      = Easing.Ease(cacheEasingTypeY, cacheEasingPhaseY, block.y, cacheY * block.y, offset, cacheInterval);
            var z      = Easing.Ease(cacheEasingTypeZ, cacheEasingPhaseZ, 0, cacheZ, offset, cacheInterval);
//			block.transform.localPosition = Vector3.Lerp(block.transform.localPosition, new Vector3(x, y, z), UnityEngine.Time.deltaTime * damping);
            block.transform.localPosition = new Vector3(x, y, z);
        }
        else              // Miss
        {
            AudioFxPool.Play(badAudioConfigs[Random.Range(0, badAudioConfigs.Length)], block.transform.position);
            FlashFxPool.Flash(badFlashFxConfig, block.transform.position);

            uiScoreText.material    = badMaterial;
            uiComboText.material    = badMaterial;
            uiIncText.text          = "";
            uiComboText.text        = "x 0 x";
            uiComboTextTween.isDead = false;
            uiComboTextTween.time   = 0;

            block.shouldDie         = true;
            block.shouldDieSilently = true;
            combo = 0;
        }

        if (block.isClosed && MusicPlayer.LiveTime - block.startTime < cacheInterval) // Detect collision
        {
            if (block.side == Side.Left)                                              // Left hand
            {
                if (Vector3.Dot(leftTip.velocity, -block.transform.up) > block.minDyingSpeed)
                {
                    DetectCollision(leftTip, block);
                }
            }
            else                  // Right hand
            {
                if (Vector3.Dot(rightTip.velocity, -block.transform.up) > block.minDyingSpeed)
                {
                    DetectCollision(rightTip, block);
                }
            }
        }
    }