zhanghaijun лет назад: 2
Родитель
Сommit
f76898593d

+ 7 - 3
train-web/src/layout/index.vue

@@ -1,16 +1,19 @@
 <template>
   <el-container>
-    <el-aside width="auto">
+    <el-aside width="auto" v-if="!isCollapse">
       <side-bar :collapse="isCollapse"></side-bar>
     </el-aside>
     <el-container>
-      <el-header style="display: flex; align-items: center; justify-content: center; padding-left: 4vh; font-size: larger; font-weight: 700"> {{ routeName }} </el-header>
+      <el-header style="display: flex; align-items: center; justify-content: center; padding-left: 4vh; font-size: larger; font-weight: 700">
+        <icon-svg icon-class="druid" style="cursor: pointer" @click="()=>isCollapse=!isCollapse"/>
+        <div style="flex: 1;text-align: center">{{ routeName }}</div>
+      </el-header>
       <el-main>
         <router-view v-slot="{ Component, route }">
           <transition mode="out-in">
             <div style="width: 100%; height: 100%; overflow: hidden">
               <keep-alive>
-                <component v-if="!route.meta.link" :is="Component" :key="route.path" />
+                <component v-if="!route.meta.link" :is="Component" :key="route.path"/>
               </keep-alive>
             </div>
           </transition>
@@ -21,6 +24,7 @@
 </template>
 <script setup lang="ts">
 import SideBar from "./asideBar.vue";
+
 const isCollapse = ref<boolean>(false);
 
 const route = useRoute();

+ 33 - 0
train-web/src/pages/train/controller/DrawTrack.ts

@@ -0,0 +1,33 @@
+import { crtPath } from '../core/Utils'
+
+export class DrawTrack {
+
+  drawSymbol(ctx: CanvasRenderingContext2D, x: number = 0, y: number = 0, isHead: boolean = true) {
+    const space: number = 5;
+    ctx.save();
+    ctx.beginPath();
+    if (isHead) {
+      crtPath(ctx, [x - space, y - space, x, y - space, x, y + space, x - space, y + space])
+    } else {
+      crtPath(ctx, [x + space, y - space, x, y - space, x, y + space, x + space, y + space])
+    }
+    ctx.stroke();
+  }
+  drawLine(ctx: CanvasRenderingContext2D) {
+    // 上行线
+    this.drawSymbol(ctx, 100, 100);
+    ctx.save();
+    ctx.beginPath();
+    crtPath(ctx, [100, 100, 1024, 100])
+    ctx.stroke();
+    this.drawSymbol(ctx, 1024, 100, false);
+
+    // 下行线
+    this.drawSymbol(ctx, 100, 300);
+    ctx.save();
+    ctx.beginPath();
+    crtPath(ctx, [100, 300, 1024, 300])
+    ctx.stroke();
+    this.drawSymbol(ctx, 1024, 300, false);
+  }
+}

+ 0 - 3
train-web/src/pages/train/core/DrawTrack.ts

@@ -1,3 +0,0 @@
-class DrawTrack {
-
-}

+ 427 - 0
train-web/src/pages/train/core/MathUtils.ts

@@ -0,0 +1,427 @@
+const _lut = [
+	'00',
+	'01',
+	'02',
+	'03',
+	'04',
+	'05',
+	'06',
+	'07',
+	'08',
+	'09',
+	'0a',
+	'0b',
+	'0c',
+	'0d',
+	'0e',
+	'0f',
+	'10',
+	'11',
+	'12',
+	'13',
+	'14',
+	'15',
+	'16',
+	'17',
+	'18',
+	'19',
+	'1a',
+	'1b',
+	'1c',
+	'1d',
+	'1e',
+	'1f',
+	'20',
+	'21',
+	'22',
+	'23',
+	'24',
+	'25',
+	'26',
+	'27',
+	'28',
+	'29',
+	'2a',
+	'2b',
+	'2c',
+	'2d',
+	'2e',
+	'2f',
+	'30',
+	'31',
+	'32',
+	'33',
+	'34',
+	'35',
+	'36',
+	'37',
+	'38',
+	'39',
+	'3a',
+	'3b',
+	'3c',
+	'3d',
+	'3e',
+	'3f',
+	'40',
+	'41',
+	'42',
+	'43',
+	'44',
+	'45',
+	'46',
+	'47',
+	'48',
+	'49',
+	'4a',
+	'4b',
+	'4c',
+	'4d',
+	'4e',
+	'4f',
+	'50',
+	'51',
+	'52',
+	'53',
+	'54',
+	'55',
+	'56',
+	'57',
+	'58',
+	'59',
+	'5a',
+	'5b',
+	'5c',
+	'5d',
+	'5e',
+	'5f',
+	'60',
+	'61',
+	'62',
+	'63',
+	'64',
+	'65',
+	'66',
+	'67',
+	'68',
+	'69',
+	'6a',
+	'6b',
+	'6c',
+	'6d',
+	'6e',
+	'6f',
+	'70',
+	'71',
+	'72',
+	'73',
+	'74',
+	'75',
+	'76',
+	'77',
+	'78',
+	'79',
+	'7a',
+	'7b',
+	'7c',
+	'7d',
+	'7e',
+	'7f',
+	'80',
+	'81',
+	'82',
+	'83',
+	'84',
+	'85',
+	'86',
+	'87',
+	'88',
+	'89',
+	'8a',
+	'8b',
+	'8c',
+	'8d',
+	'8e',
+	'8f',
+	'90',
+	'91',
+	'92',
+	'93',
+	'94',
+	'95',
+	'96',
+	'97',
+	'98',
+	'99',
+	'9a',
+	'9b',
+	'9c',
+	'9d',
+	'9e',
+	'9f',
+	'a0',
+	'a1',
+	'a2',
+	'a3',
+	'a4',
+	'a5',
+	'a6',
+	'a7',
+	'a8',
+	'a9',
+	'aa',
+	'ab',
+	'ac',
+	'ad',
+	'ae',
+	'af',
+	'b0',
+	'b1',
+	'b2',
+	'b3',
+	'b4',
+	'b5',
+	'b6',
+	'b7',
+	'b8',
+	'b9',
+	'ba',
+	'bb',
+	'bc',
+	'bd',
+	'be',
+	'bf',
+	'c0',
+	'c1',
+	'c2',
+	'c3',
+	'c4',
+	'c5',
+	'c6',
+	'c7',
+	'c8',
+	'c9',
+	'ca',
+	'cb',
+	'cc',
+	'cd',
+	'ce',
+	'cf',
+	'd0',
+	'd1',
+	'd2',
+	'd3',
+	'd4',
+	'd5',
+	'd6',
+	'd7',
+	'd8',
+	'd9',
+	'da',
+	'db',
+	'dc',
+	'dd',
+	'de',
+	'df',
+	'e0',
+	'e1',
+	'e2',
+	'e3',
+	'e4',
+	'e5',
+	'e6',
+	'e7',
+	'e8',
+	'e9',
+	'ea',
+	'eb',
+	'ec',
+	'ed',
+	'ee',
+	'ef',
+	'f0',
+	'f1',
+	'f2',
+	'f3',
+	'f4',
+	'f5',
+	'f6',
+	'f7',
+	'f8',
+	'f9',
+	'fa',
+	'fb',
+	'fc',
+	'fd',
+	'fe',
+	'ff',
+]
+
+let _seed = 1234567
+
+const DEG2RAD = Math.PI / 180
+const RAD2DEG = 180 / Math.PI
+
+// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
+function generateUUID() {
+	const d0 = (Math.random() * 0xffffffff) | 0
+	const d1 = (Math.random() * 0xffffffff) | 0
+	const d2 = (Math.random() * 0xffffffff) | 0
+	const d3 = (Math.random() * 0xffffffff) | 0
+	const uuid =
+		_lut[d0 & 0xff] +
+		_lut[(d0 >> 8) & 0xff] +
+		_lut[(d0 >> 16) & 0xff] +
+		_lut[(d0 >> 24) & 0xff] +
+		'-' +
+		_lut[d1 & 0xff] +
+		_lut[(d1 >> 8) & 0xff] +
+		'-' +
+		_lut[((d1 >> 16) & 0x0f) | 0x40] +
+		_lut[(d1 >> 24) & 0xff] +
+		'-' +
+		_lut[(d2 & 0x3f) | 0x80] +
+		_lut[(d2 >> 8) & 0xff] +
+		'-' +
+		_lut[(d2 >> 16) & 0xff] +
+		_lut[(d2 >> 24) & 0xff] +
+		_lut[d3 & 0xff] +
+		_lut[(d3 >> 8) & 0xff] +
+		_lut[(d3 >> 16) & 0xff] +
+		_lut[(d3 >> 24) & 0xff]
+
+	// .toLowerCase() here flattens concatenated strings to save heap memory space.
+	return uuid.toLowerCase()
+}
+
+function clamp(value: number, min: number, max: number) {
+	return Math.max(min, Math.min(max, value))
+}
+
+// compute euclidean modulo of m % n
+// https://en.wikipedia.org/wiki/Modulo_operation
+function euclideanModulo(n: number, m: number) {
+	return ((n % m) + m) % m
+}
+
+// Linear mapping from range <a1, a2> to range <b1, b2>
+function mapLinear(x: number, a1: number, a2: number, b1: number, b2: number) {
+	return b1 + ((x - a1) * (b2 - b1)) / (a2 - a1)
+}
+
+// https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/
+function inverseLerp(x: number, y: number, value: number) {
+	if (x !== y) {
+		return (value - x) / (y - x)
+	} else {
+		return 0
+	}
+}
+
+// https://en.wikipedia.org/wiki/Linear_interpolation
+function lerp(x: number, y: number, t: number) {
+	return (1 - t) * x + t * y
+}
+
+// http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
+function damp(x: number, y: number, lambda: number, dt: number) {
+	return lerp(x, y, 1 - Math.exp(-lambda * dt))
+}
+
+// https://www.desmos.com/calculator/vcsjnyz7x4
+function pingpong(x: number, length = 1) {
+	return length - Math.abs(euclideanModulo(x, length * 2) - length)
+}
+
+// http://en.wikipedia.org/wiki/Smoothstep
+function smoothstep(x: number, min: number, max: number) {
+	if (x <= min) return 0
+	if (x >= max) return 1
+
+	x = (x - min) / (max - min)
+
+	return x * x * (3 - 2 * x)
+}
+
+function smootherstep(x: number, min: number, max: number) {
+	if (x <= min) return 0
+	if (x >= max) return 1
+
+	x = (x - min) / (max - min)
+
+	return x * x * x * (x * (x * 6 - 15) + 10)
+}
+
+// Random integer from <low, high> interval
+function randInt(low: number, high: number) {
+	return low + Math.floor(Math.random() * (high - low + 1))
+}
+
+// Random float from <low, high> interval
+function randFloat(low: number, high: number) {
+	return low + Math.random() * (high - low)
+}
+
+// Random float from <-range/2, range/2> interval
+function randFloatSpread(range: number) {
+	return range * (0.5 - Math.random())
+}
+
+// Deterministic pseudo-random float in the interval [ 0, 1 ]
+function seededRandom(s: number) {
+	if (s !== undefined) _seed = s
+
+	// Mulberry32 generator
+
+	let t = (_seed += 0x6d2b79f5)
+
+	t = Math.imul(t ^ (t >>> 15), t | 1)
+
+	t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
+
+	return ((t ^ (t >>> 14)) >>> 0) / 4294967296
+}
+
+function degToRad(degrees: number) {
+	return degrees * DEG2RAD
+}
+
+function radToDeg(radians: number) {
+	return radians * RAD2DEG
+}
+
+function isPowerOfTwo(value: number) {
+	return (value & (value - 1)) === 0 && value !== 0
+}
+
+function ceilPowerOfTwo(value: number) {
+	return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2))
+}
+
+function floorPowerOfTwo(value: number) {
+	return Math.pow(2, Math.floor(Math.log(value) / Math.LN2))
+}
+
+export {
+	DEG2RAD,
+	RAD2DEG,
+	generateUUID,
+	clamp,
+	euclideanModulo,
+	mapLinear,
+	inverseLerp,
+	lerp,
+	damp,
+	pingpong,
+	smoothstep,
+	smootherstep,
+	randInt,
+	randFloat,
+	randFloatSpread,
+	seededRandom,
+	degToRad,
+	radToDeg,
+	isPowerOfTwo,
+	ceilPowerOfTwo,
+	floorPowerOfTwo,
+}

+ 307 - 0
train-web/src/pages/train/core/Matrix3.ts

@@ -0,0 +1,307 @@
+class Matrix3 {
+  elements: number[] = [1, 0, 0, 0, 1, 0, 0, 0, 1]
+  set(
+    n11: number,
+    n12: number,
+    n13: number,
+    n21: number,
+    n22: number,
+    n23: number,
+    n31: number,
+    n32: number,
+    n33: number
+  ) {
+    const te = this.elements
+    te[0] = n11
+    te[1] = n21
+    te[2] = n31
+    te[3] = n12
+    te[4] = n22
+    te[5] = n32
+    te[6] = n13
+    te[7] = n23
+    te[8] = n33
+    return this
+  }
+  identity() {
+    this.set(1, 0, 0, 0, 1, 0, 0, 0, 1)
+
+    return this
+  }
+
+  copy(m: Matrix3) {
+    const te = this.elements
+    const me = m.elements
+
+    te[0] = me[0]
+    te[1] = me[1]
+    te[2] = me[2]
+    te[3] = me[3]
+    te[4] = me[4]
+    te[5] = me[5]
+    te[6] = me[6]
+    te[7] = me[7]
+    te[8] = me[8]
+
+    return this
+  }
+
+  setFromMatrix4(m: Matrix3) {
+    const me = m.elements
+
+    this.set(me[0], me[4], me[8], me[1], me[5], me[9], me[2], me[6], me[10])
+
+    return this
+  }
+
+  multiply(m: Matrix3) {
+    return this.multiplyMatrices(this, m)
+  }
+
+  premultiply(m: Matrix3) {
+    return this.multiplyMatrices(m, this)
+  }
+
+  multiplyMatrices(a: Matrix3, b: Matrix3) {
+    const ae = a.elements
+    const be = b.elements
+    const te = this.elements
+
+    const a11 = ae[0],
+      a12 = ae[3],
+      a13 = ae[6]
+    const a21 = ae[1],
+      a22 = ae[4],
+      a23 = ae[7]
+    const a31 = ae[2],
+      a32 = ae[5],
+      a33 = ae[8]
+
+    const b11 = be[0],
+      b12 = be[3],
+      b13 = be[6]
+    const b21 = be[1],
+      b22 = be[4],
+      b23 = be[7]
+    const b31 = be[2],
+      b32 = be[5],
+      b33 = be[8]
+
+    te[0] = a11 * b11 + a12 * b21 + a13 * b31
+    te[3] = a11 * b12 + a12 * b22 + a13 * b32
+    te[6] = a11 * b13 + a12 * b23 + a13 * b33
+
+    te[1] = a21 * b11 + a22 * b21 + a23 * b31
+    te[4] = a21 * b12 + a22 * b22 + a23 * b32
+    te[7] = a21 * b13 + a22 * b23 + a23 * b33
+
+    te[2] = a31 * b11 + a32 * b21 + a33 * b31
+    te[5] = a31 * b12 + a32 * b22 + a33 * b32
+    te[8] = a31 * b13 + a32 * b23 + a33 * b33
+
+    return this
+  }
+
+  multiplyScalar(s: number) {
+    const te = this.elements
+
+    te[0] *= s
+    te[3] *= s
+    te[6] *= s
+    te[1] *= s
+    te[4] *= s
+    te[7] *= s
+    te[2] *= s
+    te[5] *= s
+    te[8] *= s
+
+    return this
+  }
+
+  determinant() {
+    const te = this.elements
+
+    const a = te[0],
+      b = te[1],
+      c = te[2],
+      d = te[3],
+      e = te[4],
+      f = te[5],
+      g = te[6],
+      h = te[7],
+      i = te[8]
+
+    return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g
+  }
+
+  invert() {
+    const te = this.elements,
+      n11 = te[0],
+      n21 = te[1],
+      n31 = te[2],
+      n12 = te[3],
+      n22 = te[4],
+      n32 = te[5],
+      n13 = te[6],
+      n23 = te[7],
+      n33 = te[8],
+      t11 = n33 * n22 - n32 * n23,
+      t12 = n32 * n13 - n33 * n12,
+      t13 = n23 * n12 - n22 * n13,
+      det = n11 * t11 + n21 * t12 + n31 * t13
+
+    if (det === 0) return this.set(0, 0, 0, 0, 0, 0, 0, 0, 0)
+
+    const detInv = 1 / det
+
+    te[0] = t11 * detInv
+    te[1] = (n31 * n23 - n33 * n21) * detInv
+    te[2] = (n32 * n21 - n31 * n22) * detInv
+
+    te[3] = t12 * detInv
+    te[4] = (n33 * n11 - n31 * n13) * detInv
+    te[5] = (n31 * n12 - n32 * n11) * detInv
+
+    te[6] = t13 * detInv
+    te[7] = (n21 * n13 - n23 * n11) * detInv
+    te[8] = (n22 * n11 - n21 * n12) * detInv
+
+    return this
+  }
+
+  transpose() {
+    let tmp
+    const m = this.elements
+
+    tmp = m[1]
+    m[1] = m[3]
+    m[3] = tmp
+    tmp = m[2]
+    m[2] = m[6]
+    m[6] = tmp
+    tmp = m[5]
+    m[5] = m[7]
+    m[7] = tmp
+
+    return this
+  }
+
+  transposeIntoArray(r: number[]) {
+    const m = this.elements
+
+    r[0] = m[0]
+    r[1] = m[3]
+    r[2] = m[6]
+    r[3] = m[1]
+    r[4] = m[4]
+    r[5] = m[7]
+    r[6] = m[2]
+    r[7] = m[5]
+    r[8] = m[8]
+
+    return this
+  }
+
+  setUvTransform(
+    tx: number,
+    ty: number,
+    sx: number,
+    sy: number,
+    rotation: number,
+    cx: number,
+    cy: number
+  ) {
+    const c = Math.cos(rotation)
+    const s = Math.sin(rotation)
+
+    this.set(
+      sx * c,
+      sx * s,
+      -sx * (c * cx + s * cy) + cx + tx,
+      -sy * s,
+      sy * c,
+      -sy * (-s * cx + c * cy) + cy + ty,
+      0,
+      0,
+      1
+    )
+
+    return this
+  }
+
+  scale(sx: number, sy: number = sx) {
+    this.premultiply(_m3.makeScale(sx, sy))
+
+    return this
+  }
+
+  rotate(theta: number) {
+    this.premultiply(_m3.makeRotation(theta))
+
+    return this
+  }
+
+  translate(tx: number, ty: number) {
+    this.premultiply(_m3.makeTranslation(tx, ty))
+
+    return this
+  }
+
+  // for 2D Transforms
+
+  makeTranslation(x: number, y: number) {
+    this.set(1, 0, x, 0, 1, y, 0, 0, 1)
+
+    return this
+  }
+
+  makeRotation(theta: number) {
+    // counterclockwise
+
+    const c = Math.cos(theta)
+    const s = Math.sin(theta)
+
+    this.set(c, -s, 0, s, c, 0, 0, 0, 1)
+
+    return this
+  }
+
+  makeScale(x: number, y: number) {
+    this.set(x, 0, 0, 0, y, 0, 0, 0, 1)
+
+    return this
+  }
+
+  setTranslation(x: number, y: number) {
+    const { elements } = this
+    elements[6] = x
+    elements[7] = y
+
+    return this
+  }
+
+  equals(matrix: Matrix3) {
+    const te = this.elements
+    const me = matrix.elements
+
+    for (let i = 0; i < 9; i++) {
+      if (te[i] !== me[i]) return false
+    }
+
+    return true
+  }
+
+  fromArray(array: number[], offset = 0) {
+    for (let i = 0; i < 9; i++) {
+      this.elements[i] = array[i + offset]
+    }
+
+    return this
+  }
+
+  clone() {
+    return new Matrix3().fromArray(this.elements)
+  }
+}
+const _m3 = new Matrix3()
+export { Matrix3 }

+ 12 - 0
train-web/src/pages/train/core/Utils.ts

@@ -0,0 +1,12 @@
+import { Vector2 } from './Vector2'
+
+/* 创建路径 */
+export function crtPath(ctx: CanvasRenderingContext2D, vertices: number[], closePath = false) {
+  const p0 = new Vector2(vertices[0], vertices[1])
+  ctx.moveTo(p0.x, p0.y)
+  for (let i = 2, len = vertices.length; i < len; i += 2) {
+    const pn = new Vector2(vertices[i], vertices[i + 1])
+    ctx.lineTo(pn.x, pn.y)
+  }
+  closePath && ctx.closePath()
+}

+ 375 - 0
train-web/src/pages/train/core/Vector2.ts

@@ -0,0 +1,375 @@
+import { Matrix3 } from './Matrix3'
+
+class Vector2 {
+  x = 0
+  y = 0
+  readonly isVector2 = true
+  constructor(x = 0, y = x) {
+    this.x = x
+    this.y = y
+  }
+
+  get width() {
+    return this.x
+  }
+
+  set width(value) {
+    this.x = value
+  }
+
+  get height() {
+    return this.y
+  }
+
+  set height(value) {
+    this.y = value
+  }
+
+  set(x: number, y = x) {
+    this.x = x
+    this.y = y
+
+    return this
+  }
+
+  setScalar(scalar: number) {
+    this.x = scalar
+    this.y = scalar
+
+    return this
+  }
+
+  setX(x: number) {
+    this.x = x
+
+    return this
+  }
+
+  setY(y: number) {
+    this.y = y
+
+    return this
+  }
+
+  setComponent(index: number, value: number) {
+    switch (index) {
+      case 0:
+        this.x = value
+        break
+      case 1:
+        this.y = value
+        break
+      default:
+        throw new Error('index is out of range: ' + index)
+    }
+
+    return this
+  }
+
+  getComponent(index: number) {
+    switch (index) {
+      case 0:
+        return this.x
+      case 1:
+        return this.y
+      default:
+        throw new Error('index is out of range: ' + index)
+    }
+  }
+
+  clone() {
+    return new Vector2(this.x, this.y)
+  }
+
+  copy(v: Vector2) {
+    this.x = v.x
+    this.y = v.y
+
+    return this
+  }
+
+  add(v: Vector2) {
+    this.x += v.x
+    this.y += v.y
+
+    return this
+  }
+
+  addScalar(s: number) {
+    this.x += s
+    this.y += s
+
+    return this
+  }
+
+  addVectors(a: Vector2, b: Vector2) {
+    this.x = a.x + b.x
+    this.y = a.y + b.y
+
+    return this
+  }
+
+  addScaledVector(v: Vector2, s: number) {
+    this.x += v.x * s
+    this.y += v.y * s
+
+    return this
+  }
+
+  sub(v: Vector2) {
+    this.x -= v.x
+    this.y -= v.y
+
+    return this
+  }
+
+  subScalar(s: number) {
+    this.x -= s
+    this.y -= s
+
+    return this
+  }
+
+  subVectors(a: Vector2, b: Vector2) {
+    this.x = a.x - b.x
+    this.y = a.y - b.y
+
+    return this
+  }
+
+  multiply(v: Vector2) {
+    this.x *= v.x
+    this.y *= v.y
+
+    return this
+  }
+
+  multiplyScalar(scalar: number) {
+    this.x *= scalar
+    this.y *= scalar
+
+    return this
+  }
+
+  divide(v: Vector2) {
+    this.x /= v.x
+    this.y /= v.y
+
+    return this
+  }
+
+  divideScalar(scalar: number) {
+    return this.multiplyScalar(1 / scalar)
+  }
+
+  min(v: Vector2) {
+    this.x = Math.min(this.x, v.x)
+    this.y = Math.min(this.y, v.y)
+
+    return this
+  }
+
+  max(v: Vector2) {
+    this.x = Math.max(this.x, v.x)
+    this.y = Math.max(this.y, v.y)
+
+    return this
+  }
+
+  clamp(min: Vector2, max: Vector2) {
+    // assumes min < max, componentwise
+
+    this.x = Math.max(min.x, Math.min(max.x, this.x))
+    this.y = Math.max(min.y, Math.min(max.y, this.y))
+
+    return this
+  }
+
+  clampScalar(minVal: number, maxVal: number) {
+    this.x = Math.max(minVal, Math.min(maxVal, this.x))
+    this.y = Math.max(minVal, Math.min(maxVal, this.y))
+
+    return this
+  }
+
+  clampLength(min: number, max: number) {
+    const length = this.length()
+
+    return this.divideScalar(length || 1).multiplyScalar(
+      Math.max(min, Math.min(max, length))
+    )
+  }
+
+  floor() {
+    this.x = Math.floor(this.x)
+    this.y = Math.floor(this.y)
+
+    return this
+  }
+
+  ceil() {
+    this.x = Math.ceil(this.x)
+    this.y = Math.ceil(this.y)
+
+    return this
+  }
+
+  round() {
+    this.x = Math.round(this.x)
+    this.y = Math.round(this.y)
+
+    return this
+  }
+
+  roundToZero() {
+    this.x = this.x < 0 ? Math.ceil(this.x) : Math.floor(this.x)
+    this.y = this.y < 0 ? Math.ceil(this.y) : Math.floor(this.y)
+
+    return this
+  }
+
+  negate() {
+    this.x = -this.x
+    this.y = -this.y
+
+    return this
+  }
+
+  dot(v: Vector2) {
+    return this.x * v.x + this.y * v.y
+  }
+
+  cross(v: Vector2) {
+    return this.x * v.y - this.y * v.x
+  }
+
+  lengthSq() {
+    return this.x * this.x + this.y * this.y
+  }
+
+  length() {
+    return Math.sqrt(this.x * this.x + this.y * this.y)
+  }
+
+  manhattanLength() {
+    return Math.abs(this.x) + Math.abs(this.y)
+  }
+
+  normalize() {
+    return this.divideScalar(this.length() || 1)
+  }
+
+  angle() {
+    // computes the angle in radians with respect to the positive x-axis
+
+    const angle = Math.atan2(-this.y, -this.x) + Math.PI
+
+    return angle
+  }
+
+  distanceTo(v: Vector2) {
+    return Math.sqrt(this.distanceToSquared(v))
+  }
+
+  distanceToSquared(v: Vector2) {
+    const dx = this.x - v.x,
+      dy = this.y - v.y
+    return dx * dx + dy * dy
+  }
+
+  manhattanDistanceTo(v: Vector2) {
+    return Math.abs(this.x - v.x) + Math.abs(this.y - v.y)
+  }
+
+  setLength(length: number) {
+    return this.normalize().multiplyScalar(length)
+  }
+
+  lerp(v: Vector2, alpha: number) {
+    this.x += (v.x - this.x) * alpha
+    this.y += (v.y - this.y) * alpha
+
+    return this
+  }
+
+  lerpVectors(v1: Vector2, v2: Vector2, alpha: number) {
+    this.x = v1.x + (v2.x - v1.x) * alpha
+    this.y = v1.y + (v2.y - v1.y) * alpha
+
+    return this
+  }
+
+  equals(v: Vector2) {
+    return v.x === this.x && v.y === this.y
+  }
+
+  fromArray(array: number[], offset = 0) {
+    this.x = array[offset]
+    this.y = array[offset + 1]
+
+    return this
+  }
+
+  toArray(array: number[], offset = 0) {
+    array[offset] = this.x
+    array[offset + 1] = this.y
+
+    return array
+  }
+
+  rotate(angle: number) {
+    const { x, y } = this
+    const c = Math.cos(angle)
+    const s = Math.sin(angle)
+    this.x = x * c - y * s
+    this.y = x * s + y * c
+    return this
+  }
+
+  rotateAround(center: Vector2, angle: number) {
+    const c = Math.cos(angle),
+      s = Math.sin(angle)
+
+    const x = this.x - center.x
+    const y = this.y - center.y
+
+    this.x = x * c - y * s + center.x
+    this.y = x * s + y * c + center.y
+
+    return this
+  }
+
+  random() {
+    this.x = Math.random()
+    this.y = Math.random()
+    return this
+  }
+
+  lookAt(v: Vector2) {
+    const p = new Vector2().subVectors(v, this)
+    const ang = Math.atan2(p.y, p.x)
+    if (ang < 0) {
+      return ang + Math.PI * 2
+    } else {
+      return ang
+    }
+  }
+
+  applyMatrix3(m: Matrix3) {
+    const x = this.x,
+      y = this.y
+    const e = m.elements
+
+    this.x = e[0] * x + e[3] * y + e[6]
+    this.y = e[1] * x + e[4] * y + e[7]
+
+    return this
+  }
+
+  *[Symbol.iterator]() {
+    yield this.x
+    yield this.y
+  }
+}
+
+export { Vector2 }

+ 75 - 0
train-web/src/pages/train/core/style/BasicStyle.ts

@@ -0,0 +1,75 @@
+/* 参数类型 */
+export type BasicStyleType = {
+	// 投影相关
+	shadowColor?: string | undefined
+	shadowBlur?: number
+	shadowOffsetX?: number
+	shadowOffsetY?: number
+
+	// 全局透明度
+	globalAlpha?: number | undefined
+
+	//合成相关
+	globalCompositeOperation?: GlobalCompositeOperation | undefined
+
+	// 裁剪
+	clip?: boolean
+}
+
+class BasicStyle {
+	// 投影相关
+	shadowColor: string | undefined
+	shadowBlur = 0
+	shadowOffsetX = 0
+	shadowOffsetY = 0
+
+	// 全局透明度
+	globalAlpha: number | undefined
+
+	//合成相关
+	globalCompositeOperation: GlobalCompositeOperation | undefined
+
+	// 裁剪
+	clip = false
+
+	constructor(attr: BasicStyleType = {}) {
+		this.setOption(attr)
+	}
+
+	/* 设置样式 */
+	setOption(attr: BasicStyleType = {}) {
+		Object.assign(this, attr)
+	}
+
+	/* 应用样式 */
+	apply(ctx: CanvasRenderingContext2D) {
+		const {
+			globalAlpha,
+			globalCompositeOperation,
+			shadowColor,
+			shadowBlur,
+			shadowOffsetX,
+			shadowOffsetY,
+			clip,
+		} = this
+
+		/* 投影 */
+		if (shadowColor) {
+			ctx.shadowColor = shadowColor
+			ctx.shadowBlur = shadowBlur
+			ctx.shadowOffsetX = shadowOffsetX
+			ctx.shadowOffsetY = shadowOffsetY
+		}
+
+		/* 全局合成 */
+		globalCompositeOperation &&
+			(ctx.globalCompositeOperation = globalCompositeOperation)
+
+		/*透明度合成*/
+		globalAlpha !== undefined && (ctx.globalAlpha = globalAlpha)
+
+		/* 裁剪 */
+		clip && ctx.clip()
+	}
+}
+export { BasicStyle }

+ 77 - 0
train-web/src/pages/train/core/style/StandStyle.ts

@@ -0,0 +1,77 @@
+import { BasicStyle, BasicStyleType } from './BasicStyle'
+
+/* 绘图顺序 */
+type OrderType = 0 | 1
+
+/* 绘图方法顺序 */
+type MethodsType = ['fill', 'stroke'] | ['stroke', 'fill']
+
+export type StandStyleType = {
+	strokeStyle?: string | CanvasGradient | CanvasPattern | undefined
+	fillStyle?: string | CanvasGradient | CanvasPattern | undefined
+	lineWidth?: number
+	lineDash?: number[] | undefined
+	lineDashOffset?: number
+	lineCap?: CanvasLineCap
+	lineJoin?: CanvasLineJoin
+	miterLimit?: number
+	order?: OrderType
+} & BasicStyleType
+
+class StandStyle extends BasicStyle {
+	strokeStyle: string | CanvasGradient | CanvasPattern | undefined
+	fillStyle: string | CanvasGradient | CanvasPattern | undefined
+	lineWidth: number = 1
+	lineDash: number[] | undefined
+	lineDashOffset: number = 0
+	lineCap: CanvasLineCap = 'butt'
+	lineJoin: CanvasLineJoin = 'miter'
+	miterLimit: number = 10
+
+	// 填充和描边的顺序, 默认0,即先填充再描边
+	order: OrderType = 0
+
+	constructor(attr: StandStyleType = {}) {
+		super()
+		this.setOption(attr)
+	}
+
+	/* 设置样式 */
+	setOption(attr: StandStyleType = {}) {
+		Object.assign(this, attr)
+	}
+
+	/* 获取有顺序的绘图方法 */
+	get drawOrder(): MethodsType {
+		return this.order ? ['fill', 'stroke'] : ['stroke', 'fill']
+	}
+
+	/* 应用样式 */
+	apply(ctx: CanvasRenderingContext2D) {
+		super.apply(ctx)
+		const {
+			fillStyle,
+			strokeStyle,
+			lineWidth,
+			lineCap,
+			lineJoin,
+			miterLimit,
+			lineDash,
+			lineDashOffset,
+		} = this
+
+		if (strokeStyle) {
+			ctx.strokeStyle = strokeStyle
+			ctx.lineWidth = lineWidth
+			ctx.lineCap = lineCap
+			ctx.lineJoin = lineJoin
+			ctx.miterLimit = miterLimit
+			if (lineDash) {
+				ctx.setLineDash(lineDash)
+				ctx.lineDashOffset = lineDashOffset
+			}
+		}
+		fillStyle && (ctx.fillStyle = fillStyle)
+	}
+}
+export { StandStyle }

+ 46 - 0
train-web/src/pages/train/core/style/TextStyle.ts

@@ -0,0 +1,46 @@
+import { StandStyle, StandStyleType } from './StandStyle'
+
+type FontStyle = '' | 'italic'
+type FontWeight = '' | 'bold'
+
+export type TextStyleType = {
+	fontStyle?: FontStyle
+	fontWeight?: FontWeight
+	fontSize?: number
+	fontFamily?: string
+	textAlign?: CanvasTextAlign
+	textBaseline?: CanvasTextBaseline
+} & StandStyleType
+
+class TextStyle extends StandStyle {
+	fontStyle: FontStyle = ''
+	fontWeight: FontWeight = ''
+	fontSize: number = 12
+	fontFamily: string = 'arial'
+	textAlign: CanvasTextAlign = 'start'
+	textBaseline: CanvasTextBaseline = 'alphabetic'
+
+	constructor(attr: TextStyleType = {}) {
+		super()
+		this.setOption(attr)
+	}
+
+	/* 设置样式 */
+	setOption(attr: TextStyleType = {}) {
+		Object.assign(this, attr)
+	}
+
+	/* 应用样式 */
+	apply(ctx: CanvasRenderingContext2D) {
+		super.apply(ctx)
+		this.setFont(ctx)
+		ctx.textAlign = this.textAlign
+		ctx.textBaseline = this.textBaseline
+	}
+
+	/* font 相关样式 */
+	setFont(ctx: CanvasRenderingContext2D) {
+		ctx.font = `${this.fontStyle} ${this.fontWeight} ${this.fontSize}px  ${this.fontFamily}`
+	}
+}
+export { TextStyle }

+ 9 - 53
train-web/src/pages/train/index.vue

@@ -4,45 +4,18 @@
   </div>
 </template>
 <script setup lang="ts">
-import { drawLine, drawSignal } from "./canvas";
-import { getSignAll } from "@/api/train";
+import { DrawTrack } from './controller/DrawTrack'
 
-const line_up: number = 100;
-const line_margin = 20;
-const line_down = 100;
 const contRef = ref<HTMLDivElement>();
 const canvasRef = ref<HTMLCanvasElement>();
-
-const drawTrack = function (context: CanvasRenderingContext2D) {
-  getSignAll().then(({ data }) => {
-    // 上行 从右到左
-    const lineUp = data.filter((item: any) => item.direction == "up").sort((a: any, b: any) => a.kilometer - b.kilometer)
-    for (let i = 0; i < lineUp.length; i++) {
-      var item = lineUp[i];
-      console.log(item.kilometer / 10);
-      context.fillText(item.kilometer / 10, item.kilometer / 10, line_up - 10);
-      if (i == 0) {
-        drawLine(context, [0, line_up], [item.kilometer / 10, line_up]);
-      } else if (i == lineUp.length - 1) {
-        drawLine(context, [lineUp[i - 1].kilometer / 10, line_up], [item.kilometer / 10, line_up]);
-      } else {
-        drawLine(context, [lineUp[i - 1].kilometer / 10, line_up], [item.kilometer / 10, line_up]);
-        drawLine(context, [item.kilometer / 10, line_up], [(item.kilometer + 205) / 10, line_up]);
-      }
-    }
-    // 下行 从左到右
-    const lineDown = data.filter((item: any) => item.direction == "down").sort((a: any, b: any) => a.kilometer - b.kilometer)
-
-
-
-
-  })
-}
-
+const drawTrack = new DrawTrack();
 
 onMounted(() => {
-  const cont = contRef.value;
-  const canvas = canvasRef.value;
+  const cont: HTMLDivElement | undefined = contRef.value;
+  const canvas: HTMLCanvasElement | undefined = canvasRef.value;
+  if (!canvas) {
+    return;
+  }
   canvas.width = cont?.clientWidth || 0;
   canvas.height = cont?.clientHeight || 0;
   const ctx: CanvasRenderingContext2D | null | undefined = canvas?.getContext("2d");
@@ -55,25 +28,8 @@ onMounted(() => {
   ctx.fillRect(0, 0, canvas?.width || 0, canvas?.height || 0);
   ctx.stroke();
   ctx.closePath();
-  drawTrack(ctx);
-  // 上行
-  // const up_y = 100;
-  // drawLine(ctx, [20, up_y], [1300, up_y]);
-  // for (let i = 1; i < 10; i++) {
-  //   drawSignal(ctx, 110 + i * 100, up_y);
-  // }
-  // // 下行
-  // const down_y = 300;
-  // drawLine(ctx, [20, down_y], [1300, down_y]);
-  // for (let i = 1; i < 10; i++) {
-  //   drawSignal(ctx, 110 + i * 100, down_y);
-  // }
 
-  // // 左道岔
-  // drawLine(ctx, [50, up_y], [150, down_y]);
-  // drawLine(ctx, [150, up_y], [50, down_y]);
-  // // 右道岔
-  // drawLine(ctx, [1200, up_y], [1100, down_y]);
-  // drawLine(ctx, [1100, up_y], [1200, down_y]);
+  drawTrack.drawLine(ctx);
 });
 </script>
+./controller/DrawTrack

+ 3 - 0
train-web/src/router/index.ts

@@ -56,6 +56,9 @@ const routes = [
       }, {
         path: '/demo/ImgControler',
         component: () => import('@/pages/demo/examples/ImgControler.vue'),
+      }, {
+        path: '/demo/Text2D',
+        component: () => import('@/pages/demo/examples/Text2D.vue'),
       }
     ]
   }, {

+ 4 - 1
train-web/tsconfig.json

@@ -4,6 +4,9 @@
     "paths": {
       "@/*": [
         "./src/*"
+      ],
+      "#/*": [
+        "./*"
       ]
     },
     "target": "ES2020",
@@ -41,4 +44,4 @@
       "path": "./tsconfig.node.json"
     }
   ]
-}
+}

+ 0 - 7
train-web/types/Track.ts

@@ -1,7 +0,0 @@
-class track {
-
-  drawTrack(ctx: CanvasRenderingContext2D) {
-    ctx.save();
-    // TODO 绘画轨道
-  }
-}

+ 1 - 0
train-web/vite.config.ts

@@ -41,6 +41,7 @@ export default ({ mode }: ConfigEnv) => {
     resolve: {
       alias: {
         "@": path.resolve(__dirname, "./src"),
+        "#": path.resolve(__dirname, "./"),
       },
     },
     css: {