Browse Source

旋律线算法 和性能优化

黄琪勇 8 months ago
parent
commit
629524e87c
1 changed files with 82 additions and 167 deletions
  1. 82 167
      src/page-instrument/view-detail/smoothAnimation/index.ts

+ 82 - 167
src/page-instrument/view-detail/smoothAnimation/index.ts

@@ -14,6 +14,7 @@ type smoothAnimationType = {
    canvasCtx: null | undefined | CanvasRenderingContext2D
    canvasDomWith: number
    canvasDomHeight: number
+   canvasSmoothDom: null | HTMLCanvasElement
    smoothAnimationBoxDom: null | HTMLElement
    smoothBotDom: null | HTMLElement
    osmdCanvasPageDom: null | HTMLElement
@@ -21,13 +22,14 @@ type smoothAnimationType = {
    osdmScrollDomWith: number
    osdmScrollDomOffsetLeft: number
    selectionBoxDom: null | HTMLElement
+   batePos: pointsPosType
    pointsPos: pointsPosType
    translateXNum: number
    aveSpeed: number
 }
 
-const _numberOfSegments = 58 // 中间切割线的个数
-const _canvasDomHeight = 60  // canvans 高度
+const _numberOfSegments = 60 // 中间切割线的个数
+const _canvasDomHeight = 60 // canvans 高度
 
 export const smoothAnimationState = {
    isShow: ref(false), // 是否显示
@@ -35,6 +37,7 @@ export const smoothAnimationState = {
    canvasCtx: null,
    canvasDomWith: 0,
    canvasDomHeight: _canvasDomHeight,
+   canvasSmoothDom: null,
    smoothAnimationBoxDom: null,
    smoothBotDom: null,
    osmdCanvasPageDom: null,
@@ -42,7 +45,8 @@ export const smoothAnimationState = {
    osdmScrollDomWith: 0,
    osdmScrollDomOffsetLeft: 0,
    selectionBoxDom: null,
-   pointsPos: [], // 计算之后的点坐标数组
+   batePos: [], // times 直接转换的数组
+   pointsPos: [], // 筛选之后的点坐标数组
    translateXNum: 0, // 当前谱面的translateX的距离   谱面的位置信息 由translateX和scrollLeft的偏移一起决定
    aveSpeed: 0 // 谱面的一帧的平均速度
 } as smoothAnimationType
@@ -64,11 +68,12 @@ export function initSmoothAnimation() {
    createSmoothAnimation()
    // 初始化动画数据
    const batePos = getPointsPosByBatePos()
-   console.log(batePos, "batePos")
+   smoothAnimationState.batePos = batePos
    const batePos1 = dataFilter([...batePos])
-   const batePos2 = createSmoothCurvePoints(batePos1, undefined, undefined, _numberOfSegments)
-   const batePos3 = dataFilter2(batePos, batePos2)
-   smoothAnimationState.pointsPos = batePos3
+   const batePos2 = createSmoothCurvePoints(batePos1, _numberOfSegments)
+   smoothAnimationState.pointsPos = batePos2
+   // 初始化旋律线
+   initCanvasSmooth()
    // 谱面的平均速度(因为可能有反复的情况所以实际距离要加上反复的距离)
    const canvasDomPath = batePos.reduce((path, item, index, arr) => {
       if (index !== 0) {
@@ -94,24 +99,26 @@ export function initSmoothAnimation() {
 
 // 排序
 function dataFilter(batePos: pointsPosType) {
+   // 去掉反复跳房子的数据
    const filterData = batePos.filter((item, index, array) => {
       return array.findIndex(i => i.noteId === item.noteId) === index
    })
-   // 先按 音符排序  因为音符可能有重复id
+   // 先按 音符排序
    const sortedData = filterData.sort((a, b) => a.noteId - b.noteId)
    // 再按 小节排序
    return sortedData.sort((a, b) => a.MeasureNumberXML - b.MeasureNumberXML)
 }
 
-//给原始数据赋值
-function dataFilter2(batePos1: pointsPosType, batePos2: pointsPosType) {
-   return batePos1.reduce((arr: pointsPosType, { noteId, MeasureNumberXML }) => {
-      const noteIdIndex = batePos2.findIndex(item => {
-         return noteId === item.noteId && MeasureNumberXML === item.MeasureNumberXML
-      })
-      arr.push(...batePos2.slice(noteIdIndex, noteIdIndex + _numberOfSegments + 1))
-      return arr
-   }, [])
+//根据 activeInde和进度 查找转换之后的activeInde
+function dataFindIndex(activeIndex: number, progress: number) {
+   // 百分比转为当前的index 距离个数
+   const progressCalcIndex = Math.round(progress * _numberOfSegments)
+   const { noteId, MeasureNumberXML } = smoothAnimationState.batePos[activeIndex]
+   return (
+      smoothAnimationState.pointsPos.findIndex(item => {
+         return item.noteId === noteId && item.MeasureNumberXML === MeasureNumberXML
+      }) + progressCalcIndex
+   )
 }
 
 /**
@@ -126,6 +133,7 @@ export function destroySmoothAnimation() {
       canvasCtx: null,
       canvasDomWith: 0,
       canvasDomHeight: _canvasDomHeight,
+      canvasSmoothDom: null,
       smoothAnimationBoxDom: null,
       smoothBotDom: null,
       osmdCanvasPageDom: null,
@@ -133,6 +141,7 @@ export function destroySmoothAnimation() {
       osdmScrollDomWith: 0,
       osdmScrollDomOffsetLeft: 0,
       selectionBoxDom: null,
+      batePos: [],
       pointsPos: [],
       translateXNum: 0,
       aveSpeed: 0
@@ -151,12 +160,7 @@ export function moveSmoothAnimationByPlayTime(time?: number) {
    if (currentTime <= state.fixtime) return
    if (currentTime > state.times.last()?.endtime) return
    // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
-   let nextIndex = state.activeNoteIndex + 1
-   let nextBBox = state.times[nextIndex]?.bbox
-   while (!nextBBox && nextIndex < state.times.length) {
-      nextIndex += 1
-      nextBBox = state.times[nextIndex]?.bbox
-   }
+   const nextIndex = state.activeNoteIndex + 1
    // 当前的音符和下一个音符之间的时值 (当是最后一个音符的时候,下一个音符的时间取当前音符的endtime)
    const noteDuration =
       (nextIndex > state.times.length - 1 ? state.times[state.activeNoteIndex]?.endtime : state.times[nextIndex].time) -
@@ -181,12 +185,7 @@ export function moveSmoothAnimation(progress: number, activeIndex: number, isMov
    // if (!smoothAnimationState.isShow.value) {
    //    return
    // }
-   // 计算 下一个音符index 在pointsPos 中的距离
-   const nextPointsIndex = (activeIndex + 1) * (_numberOfSegments + 1) - 1
-   // 百分比转为当前的index 距离个数
-   const progressCalcIndex = Math.round(progress * _numberOfSegments)
-   // // 当前的index
-   const nowIndex = nextPointsIndex - _numberOfSegments + progressCalcIndex
+   const nowIndex = dataFindIndex(activeIndex, progress)
    const nowPointsPos = smoothAnimationState.pointsPos[nowIndex]
    // 当x的值为null和undefinedde的时候 错误 不走下面的方法
    if (!(nowPointsPos?.x != null)) {
@@ -199,7 +198,6 @@ export function moveSmoothAnimation(progress: number, activeIndex: number, isMov
          x: nowPointsPos.x - 18, //鸟的大小
          y: nowPointsPos.y - 23
       },
-      smoothAnimationState.pointsPos,
       smoothAnimationState.pointsPos.slice(0, nowIndex)
    )
    // 当移动到屏幕最右边时候 就不进行移动了    存在移动到屏幕最右边时候  有反复的情况需要屏幕移动。所以这里注释掉了
@@ -288,9 +286,9 @@ export function moveTranslateXNum(translateXNum: number) {
 /**
  * 进度条和块移动方法
  */
-function smoothAnimationMove(pos: { x: number; y: number }, pointsPos: pointsPosType, progresspointsPos?: pointsPosType) {
+function smoothAnimationMove(pos: { x: number; y: number }, progresspointsPos: pointsPosType) {
    smoothAnimationState.smoothBotDom && (smoothAnimationState.smoothBotDom.style.transform = `translate(${pos.x}px, ${pos.y}px)`)
-   smoothAnimationState.canvasCtx && drawSmoothCurve(smoothAnimationState.canvasCtx, pointsPos, progresspointsPos)
+   smoothAnimationState.canvasCtx && drawSmoothCurveProgress(smoothAnimationState.canvasCtx, progresspointsPos, "#FFC121")
 }
 
 /**
@@ -322,7 +320,11 @@ function createSmoothAnimation() {
    smoothAnimationState.canvasDomWith = osmdCanvasPageDom?.offsetWidth || 0
    smoothCanvasDom.width = smoothAnimationState.canvasDomWith
    smoothCanvasDom.height = smoothAnimationState.canvasDomHeight
-   smoothAnimationState.canvasCtx = smoothCanvasDom.getContext("2d")
+   const ctx = smoothCanvasDom.getContext("2d")!
+   smoothAnimationState.canvasCtx = ctx
+   ctx.imageSmoothingEnabled = true
+   ctx.lineCap = "round"
+   ctx.lineJoin = "round"
    // bot
    const smoothBotDom = document.createElement("div")
    smoothBotDom.className = "smoothBot"
@@ -400,152 +402,65 @@ function quantileScale(data: number[], minRange = 0, maxRange = _canvasDomHeight
 /**
  * 使用传入的曲线的顶点坐标创建平滑曲线的顶点。
  * @param  {Array}   points  曲线顶点坐标数组,
- * @param  {Float}   tension 密集程度,默认为 0.5
- * @param  {Boolean} closed  是否创建闭合曲线,默认为 false
  * @param  {Int}     numberOfSegments 平滑曲线 2 个顶点间的线段数,默认为 20
  * @return {Array}   平滑曲线的顶点坐标数组
  */
-function createSmoothCurvePoints(pointsPos: pointsPosType, tension?: number, closed?: boolean, numberOfSegments?: number) {
-   if (pointsPos.length <= 2) {
-      return pointsPos
-   }
-   tension = tension ? tension : 0.5
-   closed = closed ? true : false
-   numberOfSegments = numberOfSegments ? numberOfSegments : 20
-   let ps = pointsPos.slice(0),
-      result = [],
-      x,
-      y,
-      t1x,
-      t2x,
-      t1y,
-      t2y,
-      c1,
-      c2,
-      c3,
-      c4,
-      st,
-      t,
-      i
-   if (closed) {
-      ps.unshift(pointsPos[pointsPos.length - 1])
-      ps.unshift(pointsPos[pointsPos.length - 1])
-      ps.push(pointsPos[0])
-   } else {
-      ps.unshift(pointsPos[0])
-      ps.push(pointsPos[pointsPos.length - 1])
+function createSmoothCurvePoints(points: pointsPosType, numSegments: number) {
+   if (points.length <= 2) {
+      return points
    }
-   for (i = 1; i < ps.length - 2; i += 1) {
-      //console.log(ps[i + 1].MeasureNumberXML, ps[i - 1].MeasureNumberXML, ps[i + 2].MeasureNumberXML, ps[i].MeasureNumberXML)
-      t1x = (ps[i + 1].x - ps[i - 1].x) * tension
-      t2x = (ps[i + 2].x - ps[i].x) * tension
-      t1y = (ps[i + 1].y - ps[i - 1].y) * tension
-      t2y = (ps[i + 2].y - ps[i].y) * tension
-      // // 当中途出现反复 刚开始反复时候 53 52 22 52  (22)中途值会变小 这里强行拉大 防止算法平均值出现很大偏差
-      // if (ps[i + 1].MeasureNumberXML - ps[i + 2].MeasureNumberXML > 1) {
-      //    const nowNumberXML = ps[i + 1].MeasureNumberXML + 1
-      //    //在当前值的情况下 向前一位
-      //    let index = ps.findIndex(item => {
-      //       return nowNumberXML === item.MeasureNumberXML
-      //    })
-      //    // 查询不到index时候取当前值
-      //    index === -1 && (index = i + 1)
-      //    t2x = (ps[index].x - ps[i].x) * tension
-      // }
-      // // 当中途出现反复 结束反复时候 22 53 22 22  (53)中途值会变大 这里强行缩小 防止算法平均值出现很大偏差
-      // if (ps[i - 1].MeasureNumberXML - ps[i].MeasureNumberXML > 1) {
-      //    //在当前值的情况下 向后一位
-      //    const nowNumberXML = ps[i].MeasureNumberXML - 1
-      //    let index = ps.findIndex((item, index) => {
-      //       return nowNumberXML === item.MeasureNumberXML && nowNumberXML !== ps[index + 1]?.MeasureNumberXML
-      //    })
-      //    // 查询不到index时候取当前值
-      //    index === -1 && (index = i)
-      //    t1x = (ps[i + 1].x - ps[index].x) * tension
-      // }
-      // // 当中途出现跳房子 刚开始跳房子时候 35 35 54 35  (54)中途值会变大 这里强行缩小 防止算法平均值出现很大偏差
-      // if (ps[i + 1].MeasureNumberXML - ps[i + 2].MeasureNumberXML < -1) {
-      //    const nowNumberXML = ps[i + 1].MeasureNumberXML + 1
-      //    //在当前值的情况下 向前一位
-      //    let index = ps.findIndex(item => {
-      //       return nowNumberXML === item.MeasureNumberXML
-      //    })
-      //    // 查询不到index时候取当前值
-      //    index === -1 && (index = i + 1)
-      //    t2x = (ps[index].x - ps[i].x) * tension
-      // }
-      // // 当中途出现跳房子 结束跳房子时候 54 35 54 54  (35)中途值会变小 这里强行拉大 防止算法平均值出现很大偏差
-      // if (ps[i - 1].MeasureNumberXML - ps[i].MeasureNumberXML < -1) {
-      //    const nowNumberXML = ps[i].MeasureNumberXML - 1
-      //    let index = ps.findIndex((item, index) => {
-      //       return nowNumberXML === item.MeasureNumberXML && nowNumberXML !== ps[index + 1]?.MeasureNumberXML
-      //    })
-      //    // 查询不到index时候取当前值
-      //    index === -1 && (index = i)
-      //    t1x = (ps[i + 1].x - ps[index].x) * tension
-      // }
-      // const nowMeasureNumberXML = pointsPos[i - 1].MeasureNumberXML
-      // const nextMeasureNumberXML = pointsPos[i].MeasureNumberXML
-      for (t = 0; t <= numberOfSegments; t++) {
-         // // 小于1时候是反复   大于1是跳房子  不画曲线  停留
-         // if (nextMeasureNumberXML - nowMeasureNumberXML < 0 || nextMeasureNumberXML - nowMeasureNumberXML > 1) {
-         //    //console.log(x, y)
-         //    result.push({
-         //       x: x as number,
-         //       y: y as number,
-         //       MeasureNumberXML: nowMeasureNumberXML,
-         //       noteId: pointsPos[i - 1].noteId
-         //    })
-         //    continue
-         // }
-         st = t / numberOfSegments
-         c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1
-         c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2)
-         c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st
-         c4 = Math.pow(st, 3) - Math.pow(st, 2)
-         x = c1 * ps[i].x + c2 * ps[i + 1].x + c3 * t1x + c4 * t2x
-         y = c1 * ps[i].y + c2 * ps[i + 1].y + c3 * t1y + c4 * t2y
-         //console.log(x, y)
-         result.push({
-            x,
-            y,
-            // MeasureNumberXML: t === numberOfSegments ? nextMeasureNumberXML : nowMeasureNumberXML,
-            // noteId: t === numberOfSegments ? pointsPos[i].noteId : pointsPos[i - 1].noteId
-            MeasureNumberXML: pointsPos[i - 1].MeasureNumberXML,
-            noteId: pointsPos[i - 1].noteId
-         })
+   const curvePoints = []
+   for (let i = 0; i < points.length - 1; i++) {
+      const p0 = i > 0 ? points[i - 1] : points[i]
+      const p1 = points[i]
+      const p2 = points[i + 1]
+      const p3 = i !== points.length - 2 ? points[i + 2] : points[i + 1]
+      for (let j = 0; j < numSegments; j++) {
+         const t = j / numSegments
+         const t2 = t * t
+         const t3 = t2 * t
+         const x = 0.5 * (2 * p1.x + (-p0.x + p2.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3)
+         const y = 0.5 * (2 * p1.y + (-p0.y + p2.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3)
+         curvePoints.push({ x, y, MeasureNumberXML: p1.MeasureNumberXML, noteId: p1.noteId })
       }
    }
-   return result
+   return curvePoints
+}
+
+/** 初始化一条完整的旋律线dom */
+function initCanvasSmooth() {
+   const smoothDom = document.createElement("canvas")
+   smoothDom.width = smoothAnimationState.canvasDomWith
+   smoothDom.height = smoothAnimationState.canvasDomHeight
+   const smoothDomCtx = smoothDom.getContext("2d")!
+   smoothDomCtx.imageSmoothingEnabled = true
+   smoothDomCtx.lineCap = "round"
+   smoothDomCtx.lineJoin = "round"
+   // 根据坐标花线
+   drawLines(smoothDomCtx, smoothAnimationState.pointsPos, "rgba(255,255,255,0.6)")
+   smoothAnimationState.canvasSmoothDom = smoothDom
 }
+
 /**
- * 根据坐标划线
+ * 根据进度画线
  */
-function drawSmoothCurve(context: CanvasRenderingContext2D, pointsPos: pointsPosType, progresspointsPos?: pointsPosType) {
-   /* 清空 */
+function drawSmoothCurveProgress(context: CanvasRenderingContext2D, pointsPos: pointsPosType, color: string) {
    context.clearRect(0, 0, smoothAnimationState.canvasDomWith, smoothAnimationState.canvasDomHeight)
-   context.lineWidth = 2
-   context.lineJoin = "round" // 优化锯齿
-   context.lineCap = "round" // 优化锯齿
-   context.strokeStyle = "rgba(255,255,255,0.6)"
-   drawLines(context, pointsPos)
-   if (progresspointsPos?.length) {
-      context.strokeStyle = "#FFC121"
-      drawLines(context, progresspointsPos)
-   }
+   smoothAnimationState.canvasSmoothDom && context.drawImage(smoothAnimationState.canvasSmoothDom, 0, 0)
+   drawLines(context, pointsPos, color)
 }
-function drawLines(context: CanvasRenderingContext2D, points: pointsPosType) {
+
+/**
+ * 根据坐标划线
+ */
+function drawLines(context: CanvasRenderingContext2D, pointsPos: pointsPosType, color: string) {
+   if (pointsPos.length === 0) return
+   context.lineWidth = 2
+   context.strokeStyle = color
    context.beginPath()
-   context.moveTo(points[0].x, points[0].y)
-   for (let i = 1; i < points.length - 1; i++) {
-      if (Math.abs(points[i].MeasureNumberXML - points[i - 1].MeasureNumberXML) > 1) {
-         // 取消反复和跳房子连线
-         context.stroke()
-         context.beginPath()
-         context.moveTo(points[i + 1].x, points[i + 1].y)
-         continue
-      }
-      context.lineTo(points[i].x, points[i].y)
+   context.moveTo(pointsPos[0].x, pointsPos[0].y)
+   for (let i = 1; i < pointsPos.length; i++) {
+      context.lineTo(pointsPos[i].x, pointsPos[i].y)
    }
    context.stroke()
 }