Explorar el Código

Merge branch 'dev/svg' of https://gitee.com/swust_0602/ludash into dev/svg

后羿 hace 1 año
padre
commit
2d4c6f7121

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/css/svg.4a6f220b.css


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/css/svg.c9841e87.css


+ 1 - 1
dist/index.html

@@ -1 +1 @@
-<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/ludash/favicon.ico"><title>ludash</title><script defer="defer" src="/ludash/js/chunk-vendors.8ec5f1ce.js"></script><script defer="defer" src="/ludash/js/app.bb4badee.js"></script><link href="/ludash/css/app.29780cd1.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but ludash doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
+<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/ludash/favicon.ico"><title>ludash</title><script defer="defer" src="/ludash/js/chunk-vendors.8ec5f1ce.js"></script><script defer="defer" src="/ludash/js/app.d22d83fc.js"></script><link href="/ludash/css/app.29780cd1.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but ludash doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/js/app.bb4badee.js.map


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/js/app.d22d83fc.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/js/app.d22d83fc.js.map


+ 0 - 1
dist/js/http.1d3ba92f.js.map

@@ -1 +0,0 @@
-{"version":3,"file":"js/http.1d3ba92f.js","mappings":"gRAiBA,MAAMA,GAAOC,EAAAA,EAAAA,IAAK,od,8RCdlB,MAAMC,EAAc,EAEpB,O","sources":["webpack://ludash/./src/views/http.vue","webpack://ludash/./src/views/http.vue?a9ff"],"sourcesContent":["<template>\r\n  <div>\r\n    <Block\r\n      title=\"Http\"\r\n      desc=\"根据使用习惯将axios进行了二次封装,提供了更方便的cancel方法,\"\r\n    >\r\n      <p class=\"demo\">DEMO</p>\r\n      <Codemirror v-model=\"code\" :disabled=\"true\" />\r\n    </Block>\r\n  </div>\r\n</template>\r\n\r\n<script setup>\r\nimport Block from \"@/components/block.vue\";\r\nimport { ref } from \"vue\";\r\nimport { Codemirror } from \"vue-codemirror\";\r\n\r\nconst code = ref(`import { WrappedAxios } from \"ludash\";\r\n  \r\nconst http = new WrappedAxios();\r\n\r\nhttp\r\n  .beforeRequest((config) => {\r\n    config.headers.token = \"123\";\r\n    return config;\r\n  })\r\n  .afterResponse(\r\n    (response) => {\r\n      return response.data;\r\n    },\r\n    (error) => {\r\n      return Promise.reject(error);\r\n    }\r\n  );\r\n\r\nhttp.get(\"/a\", { a: 1 });\r\n\r\nconst req = http.createGetWithCancel(\"/a\", { a: 1 });\r\nreq.send();\r\n\r\nsetTimeout(() => {\r\n  req.abort();\r\n});`);\r\n</script>\r\n","import script from \"./http.vue?vue&type=script&setup=true&lang=js\"\nexport * from \"./http.vue?vue&type=script&setup=true&lang=js\"\n\nconst __exports__ = script;\n\nexport default __exports__"],"names":["code","ref","__exports__"],"sourceRoot":""}

+ 1 - 1
dist/js/http.1d3ba92f.js → dist/js/http.ac7321ae.js

@@ -1,2 +1,2 @@
 "use strict";(self["webpackChunkludash"]=self["webpackChunkludash"]||[]).push([[981],{9947:function(e,n,t){t.r(n),t.d(n,{default:function(){return d}});var r=t(3396),a=t(4870),s=t(6351),o=t(5017);const u=(0,r._)("p",{class:"demo"},"DEMO",-1);var l={__name:"http",setup(e){const n=(0,a.iH)('import { WrappedAxios } from "ludash";\n  \nconst http = new WrappedAxios();\n\nhttp\n  .beforeRequest((config) => {\n    config.headers.token = "123";\n    return config;\n  })\n  .afterResponse(\n    (response) => {\n      return response.data;\n    },\n    (error) => {\n      return Promise.reject(error);\n    }\n  );\n\nhttp.get("/a", { a: 1 });\n\nconst req = http.createGetWithCancel("/a", { a: 1 });\nreq.send();\n\nsetTimeout(() => {\n  req.abort();\n});');return(e,t)=>((0,r.wg)(),(0,r.iD)("div",null,[(0,r.Wm)(s.Z,{title:"Http",desc:"根据使用习惯将axios进行了二次封装,提供了更方便的cancel方法,"},{default:(0,r.w5)((()=>[u,(0,r.Wm)((0,a.SU)(o.Z1),{modelValue:n.value,"onUpdate:modelValue":t[0]||(t[0]=e=>n.value=e),disabled:!0},null,8,["modelValue"])])),_:1})]))}};const c=l;var d=c}}]);
-//# sourceMappingURL=http.1d3ba92f.js.map
+//# sourceMappingURL=http.ac7321ae.js.map

+ 1 - 0
dist/js/http.ac7321ae.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"js/http.ac7321ae.js","mappings":"gRAiBA,MAAMA,GAAOC,EAAAA,EAAAA,IAAK,od,8RCdlB,MAAMC,EAAc,EAEpB,O","sources":["webpack://ludash/./src/views/http.vue","webpack://ludash/./src/views/http.vue?a9ff"],"sourcesContent":["<template>\n  <div>\n    <Block\n      title=\"Http\"\n      desc=\"根据使用习惯将axios进行了二次封装,提供了更方便的cancel方法,\"\n    >\n      <p class=\"demo\">DEMO</p>\n      <Codemirror v-model=\"code\" :disabled=\"true\" />\n    </Block>\n  </div>\n</template>\n\n<script setup>\nimport Block from \"@/components/block.vue\";\nimport { ref } from \"vue\";\nimport { Codemirror } from \"vue-codemirror\";\n\nconst code = ref(`import { WrappedAxios } from \"ludash\";\n  \nconst http = new WrappedAxios();\n\nhttp\n  .beforeRequest((config) => {\n    config.headers.token = \"123\";\n    return config;\n  })\n  .afterResponse(\n    (response) => {\n      return response.data;\n    },\n    (error) => {\n      return Promise.reject(error);\n    }\n  );\n\nhttp.get(\"/a\", { a: 1 });\n\nconst req = http.createGetWithCancel(\"/a\", { a: 1 });\nreq.send();\n\nsetTimeout(() => {\n  req.abort();\n});`);\n</script>\n","import script from \"./http.vue?vue&type=script&setup=true&lang=js\"\nexport * from \"./http.vue?vue&type=script&setup=true&lang=js\"\n\nconst __exports__ = script;\n\nexport default __exports__"],"names":["code","ref","__exports__"],"sourceRoot":""}

+ 1 - 1
dist/js/queue.1da0f667.js → dist/js/queue.195593f9.js

@@ -1,2 +1,2 @@
 "use strict";(self["webpackChunkludash"]=self["webpackChunkludash"]||[]).push([[618],{2021:function(n,e,u){u.r(e),u.d(e,{default:function(){return o}});var t=u(3396),s=u(4870),a=u(6351),l=u(5017);const d=(0,t._)("p",{class:"demo"},"DEMO",-1);var i={__name:"queue",setup(n){const e=(0,s.iH)('import { Queue, nextTick } from "ludash";\n\nconst q = new Queue();\n\nfor (let index = 0; index < 10; index++) {\n  q.append({\n    async run() {\n      await nextTick(1000)\n      // 如果return false,队列则会暂停\n      // 直到下一次调用startRun才会重新开始\n      return true\n    },\n  });\n}\n\n// finished | stopped\nconst status = await q.startRun();\nconsole.log(status === "finished")');return(n,u)=>((0,t.wg)(),(0,t.iD)("div",null,[(0,t.Wm)(a.Z,{title:"Queue",desc:"这个函数用于创建一个promise队列,并按加入顺序执行"},{default:(0,t.w5)((()=>[d,(0,t.Wm)((0,s.SU)(l.Z1),{modelValue:e.value,"onUpdate:modelValue":u[0]||(u[0]=n=>e.value=n),disabled:!0},null,8,["modelValue"])])),_:1})]))}};const r=i;var o=r}}]);
-//# sourceMappingURL=queue.1da0f667.js.map
+//# sourceMappingURL=queue.195593f9.js.map

+ 1 - 0
dist/js/queue.195593f9.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"js/queue.195593f9.js","mappings":"iRAiBA,MAAMA,GAAOC,EAAAA,EAAAA,IAAK,wX,uRCdlB,MAAMC,EAAc,EAEpB,O","sources":["webpack://ludash/./src/views/queue.vue","webpack://ludash/./src/views/queue.vue?10b5"],"sourcesContent":["<template>\n  <div>\n    <Block\n      title=\"Queue\"\n      desc=\"这个函数用于创建一个promise队列,并按加入顺序执行\"\n    >\n      <p class=\"demo\">DEMO</p>\n      <Codemirror v-model=\"code\" :disabled=\"true\" />\n    </Block>\n  </div>\n</template>\n\n<script setup>\nimport Block from \"@/components/block.vue\";\nimport { ref } from \"vue\";\nimport { Codemirror } from \"vue-codemirror\";\n\nconst code = ref(`import { Queue, nextTick } from \"ludash\";\n\nconst q = new Queue();\n\nfor (let index = 0; index < 10; index++) {\n  q.append({\n    async run() {\n      await nextTick(1000)\n      // 如果return false,队列则会暂停\n      // 直到下一次调用startRun才会重新开始\n      return true\n    },\n  });\n}\n\n// finished | stopped\nconst status = await q.startRun();\nconsole.log(status === \"finished\")`);\n</script>\n","import script from \"./queue.vue?vue&type=script&setup=true&lang=js\"\nexport * from \"./queue.vue?vue&type=script&setup=true&lang=js\"\n\nconst __exports__ = script;\n\nexport default __exports__"],"names":["code","ref","__exports__"],"sourceRoot":""}

+ 0 - 1
dist/js/queue.1da0f667.js.map

@@ -1 +0,0 @@
-{"version":3,"file":"js/queue.1da0f667.js","mappings":"iRAiBA,MAAMA,GAAOC,EAAAA,EAAAA,IAAK,wX,uRCdlB,MAAMC,EAAc,EAEpB,O","sources":["webpack://ludash/./src/views/queue.vue","webpack://ludash/./src/views/queue.vue?10b5"],"sourcesContent":["<template>\r\n  <div>\r\n    <Block\r\n      title=\"Queue\"\r\n      desc=\"这个函数用于创建一个promise队列,并按加入顺序执行\"\r\n    >\r\n      <p class=\"demo\">DEMO</p>\r\n      <Codemirror v-model=\"code\" :disabled=\"true\" />\r\n    </Block>\r\n  </div>\r\n</template>\r\n\r\n<script setup>\r\nimport Block from \"@/components/block.vue\";\r\nimport { ref } from \"vue\";\r\nimport { Codemirror } from \"vue-codemirror\";\r\n\r\nconst code = ref(`import { Queue, nextTick } from \"ludash\";\r\n\r\nconst q = new Queue();\r\n\r\nfor (let index = 0; index < 10; index++) {\r\n  q.append({\r\n    async run() {\r\n      await nextTick(1000)\r\n      // 如果return false,队列则会暂停\r\n      // 直到下一次调用startRun才会重新开始\r\n      return true\r\n    },\r\n  });\r\n}\r\n\r\n// finished | stopped\r\nconst status = await q.startRun();\r\nconsole.log(status === \"finished\")`);\r\n</script>\r\n","import script from \"./queue.vue?vue&type=script&setup=true&lang=js\"\nexport * from \"./queue.vue?vue&type=script&setup=true&lang=js\"\n\nconst __exports__ = script;\n\nexport default __exports__"],"names":["code","ref","__exports__"],"sourceRoot":""}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/js/svg.861c7221.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/js/svg.861c7221.js.map


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/js/svg.ff9c7952.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/js/svg.ff9c7952.js.map


+ 5 - 0
package.json

@@ -19,7 +19,10 @@
     "axios": "^1.4.0",
     "codemirror": "^6.0.1",
     "core-js": "^3.8.3",
+    "dat.gui": "^0.7.9",
     "reset-css": "^5.0.1",
+    "three": "^0.156.1",
+    "three-addons": "^1.2.0",
     "vue": "^3.2.13",
     "vue-codemirror": "^6.1.1",
     "vue-router": "^4.0.3",
@@ -28,6 +31,8 @@
   "devDependencies": {
     "@babel/core": "^7.12.16",
     "@babel/eslint-parser": "^7.12.16",
+    "@types/dat.gui": "^0.7.10",
+    "@types/three": "^0.155.1",
     "@typescript-eslint/eslint-plugin": "^5.60.0",
     "@typescript-eslint/parser": "^5.60.0",
     "@vue/cli-plugin-babel": "~5.0.0",

+ 10 - 5
public/index.html

@@ -1,17 +1,22 @@
 <!DOCTYPE html>
 <html lang="">
   <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
     <title><%= htmlWebpackPlugin.options.title %></title>
   </head>
   <body>
     <noscript>
-      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+      <strong
+        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
+        properly without JavaScript enabled. Please enable it to
+        continue.</strong
+      >
     </noscript>
     <div id="app"></div>
+    <script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
     <!-- built files will be auto injected -->
   </body>
 </html>

+ 2 - 0
src/components/navBar.vue

@@ -7,6 +7,8 @@
       <RouterLink to="/admin">admin</RouterLink>
       <RouterLink to="/bi">bi</RouterLink>
       <RouterLink to="/dashboard">dashboard</RouterLink>
+      <RouterLink to="/three">three</RouterLink>
+      <RouterLink to="/webgl">webgl</RouterLink>
       <RouterLink to="/flow">Flow</RouterLink>
       <RouterLink to="/install">安装</RouterLink>
       <RouterLink to="/sdk">SDK</RouterLink>

+ 26 - 0
src/router/index.js

@@ -60,6 +60,32 @@ const routes = [
       },
     ],
   },
+  {
+    path: "/webgl",
+    name: "webgl",
+    component: DynamicComponent,
+    children: [
+      {
+        path: "",
+        name: "webglIndex",
+        component: () =>
+          import(/* webpackChunkName: "webgl" */ "../views/webgl/index.vue"),
+      },
+    ],
+  },
+  {
+    path: "/three",
+    name: "three",
+    component: DynamicComponent,
+    children: [
+      {
+        path: "",
+        name: "threeIndex",
+        component: () =>
+          import(/* webpackChunkName: "three" */ "../views/three/index.vue"),
+      },
+    ],
+  },
   {
     path: "/sdk",
     name: "sdk",

+ 1 - 0
src/tsconfig.json

@@ -11,6 +11,7 @@
     "noUnusedLocals": true,
     "allowJs": true,
     "moduleResolution": "node",
+    "noEmit": true,
     "paths": {
       "@/*": ["*"]
     },

+ 0 - 1
src/views/svg/components/bar.vue

@@ -22,7 +22,6 @@
 </template>
 
 <script setup>
-import { defineEmits } from "vue";
 const emits = defineEmits(["createRect", "createNode"]);
 
 function createNode(e, config) {

+ 62 - 0
src/views/svg/components/helpLine.vue

@@ -0,0 +1,62 @@
+<template>
+  <g>
+    <line v-if="match.matched" v-bind="match.attr" class="dash-line"></line>
+  </g>
+</template>
+
+<script setup>
+import { watch, computed } from "vue";
+import { countPointByPos } from "../utils/index";
+const props = defineProps(["lines", "curNode", "wrapSize"]);
+
+const curNodePoints = computed(() => {
+  return props.curNode
+    ? [
+        countPointByPos("top", props.curNode.pos, props.curNode.size),
+        countPointByPos("right", props.curNode.pos, props.curNode.size),
+        countPointByPos("left", props.curNode.pos, props.curNode.size),
+        countPointByPos("bottom", props.curNode.pos, props.curNode.size),
+      ]
+    : [];
+});
+
+const match = computed(() => {
+  let res = {
+    matched: false,
+    attr: null,
+  };
+  props.lines.forEach((l) => {
+    if (!res.matched) {
+      curNodePoints.value.forEach((p) => {
+        if (p.cx === l.cx && res.matched === false) {
+          res.matched = true;
+          res.attr = {
+            x1: p.cx,
+            y1: 0,
+            x2: p.cx,
+            y2: props.wrapSize.height,
+          };
+        }
+        if (p.cy === l.cy && res.matched === false) {
+          res.matched = true;
+          res.attr = {
+            x1: 0,
+            y1: p.cy,
+            x2: props.wrapSize.width,
+            y2: p.cy,
+          };
+        }
+      });
+    }
+  });
+  return res;
+});
+</script>
+
+<style lang="less" scoped>
+.dash-line {
+  stroke: #666;
+  stroke-dasharray: 3 4;
+  stroke-width: 1;
+}
+</style>

+ 0 - 1
src/views/svg/components/svgCircle.vue

@@ -20,7 +20,6 @@
 </template>
 
 <script setup>
-import { defineProps } from "vue";
 import { useFlowItem } from "./item.mixin";
 const props = defineProps([
   "pos",

+ 192 - 7
src/views/svg/components/svgLines.vue

@@ -11,7 +11,7 @@
     >
       <path d="M 0 0 L 5 2 L 0 4 z" class="trangle" />
     </marker>
-    <line
+    <path
       v-for="i in linesAttr"
       :key="i.id"
       v-bind="i"
@@ -22,25 +22,209 @@
 </template>
 
 <script setup>
-import { defineProps, computed } from "vue";
+import { computed } from "vue";
 import { countPointByPos } from "../utils/index";
 const props = defineProps(["nodes", "lines"]);
 
+function getTransitPoint(pos, point) {
+  const transitSpace = 20;
+  switch (pos) {
+    case "left":
+      return {
+        cx: point.cx - transitSpace,
+        cy: point.cy,
+      };
+    case "top":
+      return {
+        cx: point.cx,
+        cy: point.cy - transitSpace,
+      };
+    case "right":
+      return {
+        cx: point.cx + transitSpace,
+        cy: point.cy,
+      };
+    case "bottom":
+      return {
+        cx: point.cx,
+        cy: point.cy + transitSpace,
+      };
+    default:
+      break;
+  }
+}
+
+function getAnchor(start, end) {
+  const { point: startPoint, node: startNode, pos: startPos } = start;
+  const { point: endPoint, node: endNode, pos: endPos } = end;
+
+  let p1, p2;
+  const space = 20;
+  if (["left", "right"].includes(startPos)) {
+    p1 = {
+      cx: startPoint.cx,
+      cy:
+        startPoint.cy +
+        (endPoint.cy > startPoint.cy
+          ? startNode.size.height / 2 + 20
+          : -(startNode.size.height / 2 + 20)),
+    };
+  } else {
+    p1 = {
+      cx:
+        startPoint.cx +
+        (endPoint.cx > startPoint.cx
+          ? startNode.size.width / 2 + 20
+          : -(startNode.size.width / 2 + 20)),
+      cy: startPoint.cy,
+    };
+  }
+  if (["left", "right"].includes(endPos)) {
+    p2 = {
+      cx: endPoint.cx,
+      cy:
+        endPoint.cy +
+        (endPoint.cy > startPoint.cy
+          ? -(endNode.size.height / 2 + 20)
+          : endNode.size.height / 2 + 20),
+    };
+  } else {
+    p2 = {
+      cx:
+        endPoint.cx +
+        (endPoint.cx > startPoint.cx
+          ? -(endNode.size.width / 2 + 20)
+          : endNode.size.width / 2 + 20),
+      cy: endPoint.cy,
+    };
+  }
+
+  return [p1, p2];
+}
+
+function getCenterPoint(start, end) {
+  const { point: startPoint, node: startNode, pos: startPos } = start;
+  const { point: endPoint, node: endNode, pos: endPos } = end;
+  // const [p1, p2] = getAnchor(start, end);
+  // const points = [
+  //   startPoint,
+  //   p1,
+  //   {
+  //     cx: p1.cx,
+  //     cy: p2.cy,
+  //   },
+  //   p2,
+  //   endPoint,
+  // ];
+
+  // points.forEach((p, index) => {
+  //   if (index >= 2) {
+  //     const preP1 = points[index - 1];
+  //     const preP2 = points[index - 2];
+  //     if (preP1.cx === preP2.cx && preP1.cx === p.cx) {
+  //       preP2.cy = preP1.cy = Math.min(preP2.cy, preP1.cy, p.cy);
+  //     }
+  //     if (preP1.cy === preP2.cy && preP1.cy === p.cy) {
+  //       preP2.cx = preP1.cx = Math.min(preP2.cx, preP1.cx, p.cx);
+  //     }
+  //   }
+  // });
+  // return points;
+  return [startPoint, { cx: startPoint.cx, cy: endPoint.cy }, endPoint];
+  // const points = [];
+  // let p1, p2;
+  // if (["left", "right"].includes(startNode.pos)) {
+  //   p1 = {
+  //     cx: start.cx,
+  //     cy: end.cy,
+  //   };
+  // } else {
+  //   p1 = {
+  //     cx: end.cx,
+  //     cy: start.cy,
+  //   };
+  // }
+  // if (["left", "right"].includes(endNode.pos)) {
+  //   p2 = {
+  //     cx: end.cx,
+  //     cy: p1.cy,
+  //   };
+  // } else {
+  //   p2 = {
+  //     cx: p1.cx,
+  //     cy: end.cy,
+  //   };
+  // }
+  // if (start.cx < end.cx && endNode.pos === "right") {
+  //   const topSpace = Math.min(startNode.node.pos.y, endNode.node.pos.y) - 20;
+  //   return [
+  //     {
+  //       cx: start.cx,
+  //       cy: startNode.node.pos.cy + (end.cy - start.cy) / 2,
+  //     },
+  //     {
+  //       cx: end.cx,
+  //       cy: start.cy + (end.cy - start.cy) / 2,
+  //     },
+  //   ];
+  // }
+  // if (start.cy > end.cy) {
+  //   const endWidth = endNode.size.width / 2 + 20;
+  //   return [
+  //     {
+  //       cx: start.cx - endWidth,
+  //       cy: end.cy,
+  //     },
+  //     {
+  //       cx: end.cx - endWidth,
+  //       cy: end.cy,
+  //     },
+  //   ];
+  // }
+  // return [p1, p2];
+  // return [
+  //   {
+  //     cx: start.cx,
+  //     cy: end.cy,
+  //   },
+  //   {
+  //     cx: start.cx,
+  //     cy: end.cy,
+  //   },
+  // ];
+}
+
 const linesAttr = computed(() =>
   props.lines.map((line) => {
     const { targetId: startTargetId, pos: startPos } = line.start;
     const { targetId: endTargetId, pos: endPos } = line.end;
     const startNode = props.nodes.find((i) => i.id === startTargetId);
     const endNode = props.nodes.find((i) => i.id === endTargetId);
-    const endPoint = countPointByPos(endPos, endNode.pos, endNode.size);
     const startPoint = countPointByPos(startPos, startNode.pos, startNode.size);
+    const endPoint = countPointByPos(endPos, endNode.pos, endNode.size);
+
+    const startTransitPoint = getTransitPoint(startPos, startPoint);
+    const endTransitPoint = getTransitPoint(endPos, endPoint);
+    const centerPoints = getCenterPoint(
+      { node: startNode, pos: startPos, point: startTransitPoint },
+      { node: endNode, pos: endPos, point: endTransitPoint }
+    );
+
     return {
       id: `${startTargetId}-${startPos}-${endTargetId}-${endPos}`,
-      x1: startPoint.cx,
-      y1: startPoint.cy,
-      x2: endPoint.cx,
-      y2: endPoint.cy,
+      d: [
+        `M${startPoint.cx} ${startPoint.cy}`,
+        ...centerPoints.map((i) => `L${i.cx} ${i.cy}`),
+        `L${endPoint.cx} ${endPoint.cy}`,
+      ].join(" "),
     };
+    // return {
+    //   id: `${startTargetId}-${startPos}-${endTargetId}-${endPos}`,
+    //   x1: startPoint.cx,
+    //   y1: startPoint.cy,
+    //   x2: endPoint.cx,
+    //   y2: endPoint.cy,
+    // };
   })
 );
 </script>
@@ -50,6 +234,7 @@ const linesAttr = computed(() =>
   .line {
     stroke: #5ba8ff;
     stroke-width: 2;
+    fill: transparent;
   }
 
   .trangle {

+ 0 - 1
src/views/svg/components/svgRect.vue

@@ -20,7 +20,6 @@
 </template>
 
 <script setup>
-import { defineProps } from "vue";
 import { useFlowItem } from "./item.mixin";
 const props = defineProps([
   "pos",

+ 0 - 1
src/views/svg/components/svgRhombic.vue

@@ -20,7 +20,6 @@
 </template>
 
 <script setup>
-import { defineProps } from "vue";
 import { useFlowItem } from "./item.mixin";
 const props = defineProps([
   "pos",

+ 18 - 1
src/views/svg/index.vue

@@ -12,6 +12,7 @@
       :width="wrapSize.width"
       :height="wrapSize.height"
     >
+      <HelpLine :lines="helpLines" :curNode="curNode" :wrapSize="wrapSize" />
       <SvgLines :lines="lines" :nodes="nodes" />
       <template v-for="i in nodes" :key="i.id">
         <component
@@ -25,7 +26,7 @@
           @endLine="endLine"
           @cancelEndLine="cancelEndLine"
           @startDrag="startDrag"
-        ></component>
+        />
       </template>
       <line v-if="lineStartPoint" v-bind="dashLineAttr" class="dash-line" />
     </svg>
@@ -44,6 +45,7 @@ import SvgRect from "./components/svgRect.vue";
 import SvgCircle from "./components/svgCircle.vue";
 import SvgRhombic from "./components/svgRhombic.vue";
 import SvgLines from "./components/svgLines.vue";
+import HelpLine from "./components/helpLine.vue";
 import Bar from "./components/bar.vue";
 import {
   findFromArray,
@@ -87,6 +89,21 @@ const nodes = ref([
   },
 ]);
 
+const helpLines = computed(() => {
+  const lines = [];
+  nodes.value.forEach((node) => {
+    if (node.id !== curNodeId.value) {
+      lines.push(
+        countPointByPos("left", node.pos, node.size),
+        countPointByPos("top", node.pos, node.size),
+        countPointByPos("right", node.pos, node.size),
+        countPointByPos("bottom", node.pos, node.size)
+      );
+    }
+  });
+  return lines;
+});
+
 const curNode = computed(() => {
   return curNodeId.value !== 0
     ? nodes.value.find((i) => i.id === curNodeId.value)

+ 409 - 0
src/views/three/index-1.vue

@@ -0,0 +1,409 @@
+<template>
+  <div ref="wrap"></div>
+</template>
+<script setup>
+import * as THREE from "three";
+import * as SceneUtils from "three/examples/jsm/utils/SceneUtils";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
+import Stats from "three/examples/jsm/libs/stats.module";
+import * as dat from "dat.gui";
+import { onMounted, ref } from "vue";
+
+/**
+ * @type {import("vue").Ref<HTMLDivElement>}
+ */
+const wrap = ref();
+
+function getCanvasWidth() {
+  return window.innerWidth - 60;
+}
+
+function getCanvasHeight() {
+  return window.innerHeight - 150;
+}
+
+let renderer;
+function initRender() {
+  renderer = new THREE.WebGLRenderer({ antialias: true });
+  renderer.setSize(getCanvasWidth(), getCanvasHeight());
+  renderer.setPixelRatio(window.devicePixelRatio);
+  renderer.setClearColor(0x444444, 1);
+  //告诉渲染器需要阴影效果
+  renderer.shadowMap.enabled = true;
+  renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 默认的是,没有设置的这个清晰 THREE.PCFShadowMap
+  wrap.value.appendChild(renderer.domElement);
+}
+
+let camera;
+function initCamera() {
+  camera = new THREE.PerspectiveCamera(
+    50,
+    getCanvasWidth() / getCanvasHeight(),
+    0.1,
+    10000
+  );
+  camera.position.set(0, 40, 100);
+  camera.lookAt(new THREE.Vector3(0, 0, 0));
+}
+
+let scene;
+function initScene() {
+  scene = new THREE.Scene();
+}
+
+//初始化dat.GUI简化试验流程
+let gui;
+
+let hemiLight, ambientLight, directionalLight, directionalLightHelper;
+function initGui() {
+  //声明一个保存需求修改的相关数据的对象
+  gui = {
+    directionalLight: "#ffffff", //点光源
+    directionalLightIntensity: 1, //灯光强度
+    visible: true, //是否可见
+    castShadow: true,
+    exponent: 30,
+    target: "plane",
+    debug: true,
+    groundColor: "#00ff00",
+    skyColor: "#0000ff",
+    hemiLightIntensity: 0.3,
+    dLightX: -40,
+    dLightY: 60,
+    dLightZ: -10,
+  };
+  let datGui = new dat.GUI();
+  //将设置属性添加到gui当中,gui.add(对象,属性,最小值,最大值)
+
+  const ambientFolder = datGui.addFolder("环境光");
+
+  ambientFolder.addColor(gui, "skyColor").onChange(function (e) {
+    hemiLight.color = new THREE.Color(e);
+  });
+  ambientFolder.addColor(gui, "groundColor").onChange(function (e) {
+    hemiLight.groundColor = new THREE.Color(e);
+  });
+  ambientFolder.add(gui, "hemiLightIntensity", 0, 1).onChange(function (e) {
+    hemiLight.intensity = e;
+  });
+  ambientFolder.addColor(gui, "directionalLight").onChange(function (e) {
+    directionalLight.color = new THREE.Color(e);
+  });
+
+  datGui
+    .add(gui, "dLightX", {
+      left: -100,
+      center: 0,
+      right: 100,
+      // 左: -100,//可以用中文
+      // 中: 0,
+      // 右: 100
+    })
+    .onChange(function (e) {
+      directionalLight.position.setX(e);
+    });
+
+  datGui.add(gui, "dLightY", -100, 100).onChange(function (e) {
+    directionalLight.position.setY(e);
+  });
+
+  datGui.add(gui, "dLightZ", -100, 100).onChange(function (e) {
+    directionalLight.position.setZ(e);
+  });
+
+  datGui.add(gui, "directionalLightIntensity", 0, 5).onChange(function (e) {
+    directionalLight.intensity = e;
+  });
+  datGui.add(gui, "visible").onChange(function (e) {
+    directionalLight.visible = e;
+  });
+  datGui.add(gui, "castShadow").onChange(function (e) {
+    directionalLight.castShadow = e;
+  });
+  datGui.add(gui, "debug").onChange(function (e) {
+    if (e) {
+      let debug = new THREE.CameraHelper(directionalLight.shadow.camera);
+      debug.name = "debug";
+      scene.add(debug);
+    } else {
+      let debug = scene.getObjectByName("debug");
+      scene.remove(debug);
+    }
+  });
+
+  datGui.add(cube.position, "x").name("cube-x").min(-5).max(5).step(1);
+}
+
+function initLight() {
+  hemiLight = new THREE.HemisphereLight("#bcffb1", "#000000", 1);
+  scene.add(hemiLight);
+  // ambientLight = new THREE.AmbientLight("#111111");
+  // scene.add(ambientLight);
+
+  directionalLight = new THREE.DirectionalLight("#ffffff");
+  directionalLight.position.set(-40, 60, -10);
+
+  directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight);
+
+  directionalLight.shadow.camera.near = 20; //产生阴影的最近距离
+  directionalLight.shadow.camera.far = 200; //产生阴影的最远距离
+  directionalLight.shadow.camera.left = -100; //产生阴影距离位置的最左边位置
+  directionalLight.shadow.camera.right = 100; //最右边
+  directionalLight.shadow.camera.top = 100; //最上边
+  directionalLight.shadow.camera.bottom = -100; //最下面
+
+  //这两个值决定使用多少像素生成阴影 默认512
+  directionalLight.shadow.mapSize.height = 1024;
+  directionalLight.shadow.mapSize.width = 1024;
+
+  //告诉平行光需要开启阴影投射
+  directionalLight.castShadow = true;
+
+  // scene.add(directionalLightHelper);
+
+  scene.add(directionalLight);
+}
+
+let cube, plane;
+function initModel() {
+  //辅助工具
+  let helper = new THREE.AxesHelper(100);
+  scene.add(helper);
+
+  const bufferGeometry = new THREE.BufferGeometry();
+
+  bufferGeometry.attributes.position = new THREE.BufferAttribute(
+    new Float32Array([
+      -10,
+      15,
+      10, // 0
+      10,
+      15,
+      10,
+      0,
+      15,
+      -10,
+
+      -10,
+      15,
+      10, // 0
+      10,
+      15,
+      10,
+      0,
+      40,
+      0,
+
+      0,
+      40,
+      0, // 0
+      10,
+      15,
+      10,
+      0,
+      15,
+      -10,
+
+      -10,
+      15,
+      10, // 0
+      0,
+      40,
+      0, // 0
+      0,
+      15,
+      -10,
+    ]),
+    3
+  );
+
+  const buffer = new THREE.Mesh(
+    bufferGeometry,
+    new THREE.MeshBasicMaterial({
+      color: 0x0000ff, //设置材质颜色
+      transparent: true, //开启透明
+      opacity: 0.5, //设置透明度
+      wireframe: true,
+    })
+  );
+
+  buffer.castShadow = true;
+
+  scene.add(buffer);
+
+  //球体
+  let sphereGeometry = new THREE.SphereGeometry(10, 30, 30);
+
+  let material = new THREE.MeshPhongMaterial({
+    color: 0x0000ff,
+    specular: 0x4488ee,
+    shininess: 12,
+  });
+
+  let sphere = new THREE.Mesh(
+    sphereGeometry,
+    new THREE.MeshPhongMaterial({
+      color: 0xcccccc,
+      shininess: 20,
+      specular: 0xffffff,
+    })
+  );
+  sphere.position.set(-20, 20, 0);
+
+  sphere.castShadow = true;
+
+  scene.add(sphere);
+
+  const cylinderGeometry = new THREE.CylinderGeometry(5, 5, 10);
+  const cylinder = new THREE.Mesh(
+    cylinderGeometry,
+    new THREE.MeshLambertMaterial({
+      color: 0xeeeeee,
+    })
+  );
+  cylinder.castShadow = true;
+
+  cylinder.position.x = 50;
+  cylinder.position.y = 10;
+  cylinder.position.z = -10;
+
+  scene.add(cylinder);
+
+  const cylinderGeometry2 = new THREE.CylinderGeometry(0, 5, 10);
+  const cylinder2 = new THREE.Mesh(
+    cylinderGeometry2,
+    new THREE.MeshPhongMaterial({
+      color: 0xcccccc,
+      shininess: 20,
+      specular: 0xffffff,
+    })
+  );
+  cylinder2.castShadow = true;
+
+  cylinder2.position.x = 50;
+  cylinder2.position.y = 10;
+  cylinder2.position.z = -30;
+
+  scene.add(cylinder2);
+
+  //立方体
+
+  var parentCube = new THREE.Mesh(
+    new THREE.BoxGeometry(20, 20, 20, 2, 2, 2),
+    new THREE.MeshBasicMaterial({
+      color: 0x0000ff, //设置材质颜色
+      transparent: true, //开启透明
+      opacity: 0.5, //设置透明度
+      wireframe: true,
+    })
+  );
+
+  cube = new THREE.Mesh(
+    new THREE.BoxGeometry(10, 10, 10),
+    new THREE.MeshPhongMaterial({
+      color: 0xcccccc,
+      shininess: 20,
+      specular: 0xffffff,
+    })
+  );
+  cube.position.x = 0;
+  cube.position.y = 0;
+  cube.position.z = 0;
+  cube.scale.set(2, 2, 2);
+
+  //告诉立方体需要投射阴影
+  cube.castShadow = true;
+
+  console.log(parentCube);
+
+  scene.add(parentCube);
+
+  //底部平面
+  let planeGeometry = new THREE.PlaneGeometry(5000, 5000, 20, 20);
+  let planeMaterial = new THREE.MeshLambertMaterial({ color: 0xaaaaaa });
+
+  plane = new THREE.Mesh(planeGeometry, planeMaterial);
+  plane.rotation.x = -0.5 * Math.PI;
+  plane.position.y = -0;
+
+  //告诉底部平面需要接收阴影
+  plane.receiveShadow = true;
+
+  scene.add(plane);
+}
+
+//初始化性能插件
+let stats;
+function initStats() {
+  stats = new Stats();
+  document.body.appendChild(stats.dom);
+}
+
+//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
+let controls;
+function initControls() {
+  controls = new OrbitControls(camera, renderer.domElement);
+
+  // 如果使用animate方法时,将此函数删除
+  //controls.addEventListener( 'change', render );
+  // 使动画循环使用时阻尼或自转 意思是否有惯性
+  controls.enableDamping = true;
+  //动态阻尼系数 就是鼠标拖拽旋转灵敏度
+  controls.dampingFactor = 0.05;
+  //是否可以缩放
+  controls.enableZoom = true;
+  //是否自动旋转
+  controls.autoRotate = false;
+  //设置相机距离原点的最远距离
+  controls.minDistance = 10;
+  //设置相机距离原点的最远距离
+  controls.maxDistance = 300;
+  //是否开启右键拖拽
+  controls.enablePan = true;
+}
+
+function render() {
+  renderer.render(scene, camera);
+}
+
+//窗口变动触发的函数
+function onWindowResize() {
+  camera.aspect = getCanvasWidth() / getCanvasHeight();
+  camera.updateProjectionMatrix();
+  render();
+  renderer.setSize(getCanvasWidth(), getCanvasHeight());
+}
+
+function animate() {
+  //更新控制器
+  render();
+
+  //更新性能插件
+  stats.update();
+
+  controls.update();
+
+  // cube.rotation.x += 0.1;
+  cube.rotation.y += Math.PI / 100;
+
+  requestAnimationFrame(animate);
+}
+
+function draw() {
+  initRender();
+  initScene();
+  initCamera();
+  initLight();
+  initModel();
+  initControls();
+  initStats();
+
+  animate();
+  window.onresize = onWindowResize;
+}
+
+onMounted(() => {
+  draw();
+  initGui();
+});
+</script>

+ 271 - 0
src/views/three/index-2.vue

@@ -0,0 +1,271 @@
+<template>
+  <div ref="wrap"></div>
+</template>
+<script setup>
+import * as THREE from "three";
+import * as SceneUtils from "three/examples/jsm/utils/SceneUtils";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
+import Stats from "three/examples/jsm/libs/stats.module";
+import * as dat from "dat.gui";
+import { onMounted, ref } from "vue";
+import { BoxGeometry, BufferAttribute } from "three";
+
+/**
+ * @type {import("vue").Ref<HTMLDivElement>}
+ */
+const wrap = ref();
+
+function getCanvasWidth() {
+  return window.innerWidth - 60;
+}
+
+function getCanvasHeight() {
+  return window.innerHeight - 150;
+}
+
+let renderer;
+function initRender() {
+  renderer = new THREE.WebGLRenderer({ antialias: true });
+  renderer.setSize(getCanvasWidth(), getCanvasHeight());
+  renderer.setPixelRatio(window.devicePixelRatio);
+  renderer.setClearColor(0x444444, 1);
+  //告诉渲染器需要阴影效果
+  renderer.shadowMap.enabled = true;
+  renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 默认的是,没有设置的这个清晰 THREE.PCFShadowMap
+  wrap.value.appendChild(renderer.domElement);
+}
+
+let camera;
+function initCamera() {
+  camera = new THREE.PerspectiveCamera(
+    50,
+    getCanvasWidth() / getCanvasHeight(),
+    0.1,
+    10000
+  );
+  camera.position.set(40, 40, 100);
+  camera.lookAt(new THREE.Vector3(0, 0, 0));
+}
+
+let scene;
+function initScene() {
+  scene = new THREE.Scene();
+}
+
+//初始化dat.GUI简化试验流程
+let gui;
+let datGui = new dat.GUI();
+
+let hemiLight, ambientLight, directionalLight, directionalLightHelper;
+function initGui() {
+  //声明一个保存需求修改的相关数据的对象
+  gui = {
+    directionalLight: "#ffffff", //点光源
+    directionalLightIntensity: 1, //灯光强度
+    visible: true, //是否可见
+    castShadow: true,
+    exponent: 30,
+    target: "plane",
+    debug: false,
+    groundColor: "#00ff00",
+    skyColor: "#0000ff",
+    hemiLightIntensity: 0.3,
+    dLightX: -40,
+    dLightY: 60,
+    dLightZ: -10,
+  };
+
+  //将设置属性添加到gui当中,gui.add(对象,属性,最小值,最大值)
+
+  const ambientFolder = datGui.addFolder("环境光");
+
+  ambientFolder.addColor(gui, "skyColor").onChange(function (e) {
+    hemiLight.color = new THREE.Color(e);
+  });
+  ambientFolder.addColor(gui, "groundColor").onChange(function (e) {
+    hemiLight.groundColor = new THREE.Color(e);
+  });
+  ambientFolder.add(gui, "hemiLightIntensity", 0, 1).onChange(function (e) {
+    hemiLight.intensity = e;
+  });
+  ambientFolder.addColor(gui, "directionalLight").onChange(function (e) {
+    directionalLight.color = new THREE.Color(e);
+  });
+
+  datGui.add(gui, "dLightX", -100, 100).onChange(function (e) {
+    directionalLight.position.setX(e);
+  });
+
+  datGui.add(gui, "dLightY", -100, 100).onChange(function (e) {
+    directionalLight.position.setY(e);
+  });
+
+  datGui.add(gui, "dLightZ", -100, 100).onChange(function (e) {
+    directionalLight.position.setZ(e);
+  });
+
+  datGui.add(gui, "directionalLightIntensity", 0, 5).onChange(function (e) {
+    directionalLight.intensity = e;
+  });
+  datGui.add(gui, "visible").onChange(function (e) {
+    directionalLight.visible = e;
+  });
+  datGui.add(gui, "castShadow").onChange(function (e) {
+    directionalLight.castShadow = e;
+  });
+  datGui.add(gui, "debug").onChange(function (e) {
+    if (e) {
+      let debug = new THREE.CameraHelper(directionalLight.shadow.camera);
+      debug.name = "debug";
+      scene.add(debug);
+    } else {
+      let debug = scene.getObjectByName("debug");
+      scene.remove(debug);
+    }
+  });
+}
+
+function initLight() {
+  hemiLight = new THREE.HemisphereLight("#bcffb1", "#000000", 1);
+  scene.add(hemiLight);
+  // ambientLight = new THREE.AmbientLight("#111111");
+  // scene.add(ambientLight);
+
+  directionalLight = new THREE.DirectionalLight("#ffffff");
+  directionalLight.position.set(-40, 60, -10);
+
+  directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight);
+
+  directionalLight.shadow.camera.near = 20; //产生阴影的最近距离
+  directionalLight.shadow.camera.far = 200; //产生阴影的最远距离
+  directionalLight.shadow.camera.left = -100; //产生阴影距离位置的最左边位置
+  directionalLight.shadow.camera.right = 100; //最右边
+  directionalLight.shadow.camera.top = 100; //最上边
+  directionalLight.shadow.camera.bottom = -100; //最下面
+
+  //这两个值决定使用多少像素生成阴影 默认512
+  directionalLight.shadow.mapSize.height = 1024;
+  directionalLight.shadow.mapSize.width = 1024;
+
+  //告诉平行光需要开启阴影投射
+  directionalLight.castShadow = true;
+
+  // scene.add(directionalLightHelper);
+  scene.add(directionalLight);
+}
+
+let cube, plane;
+function initModel() {
+  //辅助工具
+  let helper = new THREE.AxesHelper(100);
+  scene.add(helper);
+
+  scene.add(
+    new THREE.Mesh(new BoxGeometry(10, 10, 10), [
+      new THREE.MeshBasicMaterial({
+        color: 0x0000ff,
+      }),
+      new THREE.MeshBasicMaterial({
+        color: 0xff00ff,
+      }),
+      new THREE.MeshBasicMaterial({
+        color: 0x00ffff,
+      }),
+      new THREE.MeshBasicMaterial({
+        color: 0xff0000,
+      }),
+      new THREE.MeshBasicMaterial({
+        color: 0x00ff00,
+      }),
+      new THREE.MeshBasicMaterial({
+        color: 0x0f000f,
+      }),
+    ])
+  );
+
+  //底部平面
+  let planeGeometry = new THREE.PlaneGeometry(5000, 5000, 20, 20);
+  let planeMaterial = new THREE.MeshLambertMaterial({ color: 0xaaaaaa });
+
+  plane = new THREE.Mesh(planeGeometry, planeMaterial);
+  plane.rotation.x = -0.5 * Math.PI;
+  plane.position.y = -0;
+
+  //告诉底部平面需要接收阴影
+  plane.receiveShadow = true;
+
+  // scene.add(plane);
+}
+
+//初始化性能插件
+let stats;
+function initStats() {
+  stats = new Stats();
+  document.body.appendChild(stats.dom);
+}
+
+//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
+let controls;
+function initControls() {
+  controls = new OrbitControls(camera, renderer.domElement);
+
+  // 如果使用animate方法时,将此函数删除
+  //controls.addEventListener( 'change', render );
+  // 使动画循环使用时阻尼或自转 意思是否有惯性
+  controls.enableDamping = true;
+  //动态阻尼系数 就是鼠标拖拽旋转灵敏度
+  controls.dampingFactor = 0.05;
+  //是否可以缩放
+  controls.enableZoom = true;
+  //是否自动旋转
+  controls.autoRotate = false;
+  //设置相机距离原点的最远距离
+  controls.minDistance = 10;
+  //设置相机距离原点的最远距离
+  controls.maxDistance = 300;
+  //是否开启右键拖拽
+  controls.enablePan = true;
+}
+
+function render() {
+  renderer.render(scene, camera);
+}
+
+//窗口变动触发的函数
+function onWindowResize() {
+  camera.aspect = getCanvasWidth() / getCanvasHeight();
+  camera.updateProjectionMatrix();
+  render();
+  renderer.setSize(getCanvasWidth(), getCanvasHeight());
+}
+
+function animate() {
+  //更新控制器
+  render();
+
+  //更新性能插件
+  stats.update();
+
+  controls.update();
+
+  requestAnimationFrame(animate);
+}
+
+function draw() {
+  initRender();
+  initScene();
+  initCamera();
+  initLight();
+  initModel();
+  initControls();
+  initStats();
+
+  animate();
+  window.onresize = onWindowResize;
+}
+initGui();
+
+onMounted(() => {
+  draw();
+});
+</script>

+ 83 - 0
src/views/three/index.vue

@@ -0,0 +1,83 @@
+<template>
+  <div ref="wrap" class="three-wrap"></div>
+</template>
+<script setup>
+import * as THREE from "three";
+import * as SceneUtils from "three/examples/jsm/utils/SceneUtils";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
+import Stats from "three/examples/jsm/libs/stats.module";
+import * as dat from "dat.gui";
+import { onMounted, ref } from "vue";
+import {
+  activeShadow,
+  addResizeAdapter,
+  getScence,
+  initAxes,
+  initBoxMesh,
+  initCamera,
+  initControl,
+  initGui,
+  initHemiLight,
+  initLight,
+  initPlaneMesh,
+  initRender,
+  initStatus,
+  startAnimate,
+} from "./util";
+
+/**
+ * @type {import("vue").Ref<HTMLDivElement>}
+ */
+const wrap = ref();
+
+onMounted(() => {
+  const render = initRender();
+  const scene = getScence();
+  const camera = initCamera();
+  const axes = initAxes();
+  const status = initStatus();
+  const control = initControl(camera, render);
+
+  const plane = initPlaneMesh();
+  const box = initBoxMesh();
+  const light = initLight();
+  const hemiLight = initHemiLight();
+
+  activeShadow(box, plane, hemiLight);
+  scene.add(box).add(plane).add(light).add(hemiLight);
+
+  // const directionalLight = new THREE.DirectionalLight("#ffffff");
+  // directionalLight.position.set(-40, 60, -10);
+
+  // directionalLight.shadow.camera.near = 20; //产生阴影的最近距离
+  // directionalLight.shadow.camera.far = 200; //产生阴影的最远距离
+  // directionalLight.shadow.camera.left = -100; //产生阴影距离位置的最左边位置
+  // directionalLight.shadow.camera.right = 100; //最右边
+  // directionalLight.shadow.camera.top = 100; //最上边
+  // directionalLight.shadow.camera.bottom = -100; //最下面
+
+  // //这两个值决定使用多少像素生成阴影 默认512
+  // directionalLight.shadow.mapSize.height = 1024;
+  // directionalLight.shadow.mapSize.width = 1024;
+
+  // //告诉平行光需要开启阴影投射
+  // directionalLight.castShadow = true;
+
+  // scene.add(directionalLight);
+
+  const gui = initGui();
+
+  wrap.value.appendChild(render.domElement);
+  wrap.value.appendChild(status.dom);
+
+  scene.add(axes);
+  render.render(scene, camera);
+  addResizeAdapter(camera, render);
+
+  startAnimate(() => {
+    status.update();
+    control.update();
+    render.render(scene, camera);
+  });
+});
+</script>

+ 177 - 0
src/views/three/util.js

@@ -0,0 +1,177 @@
+import * as THREE from "three";
+import * as SceneUtils from "three/examples/jsm/utils/SceneUtils";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
+import Stats from "three/examples/jsm/libs/stats.module";
+import * as dat from "dat.gui";
+import { onMounted, ref } from "vue";
+
+export function getCanvasSize() {
+  return {
+    width: window.innerWidth - 60,
+    height: window.innerHeight - 150,
+  };
+}
+
+export function initRender() {
+  const render = new THREE.WebGLRenderer({
+    // 开起抗锯齿
+    antialias: true,
+  });
+
+  render.setSize(getCanvasSize().width, getCanvasSize().height);
+  render.setPixelRatio(window.devicePixelRatio);
+  render.setClearColor(0x666666);
+
+  render.shadowMap.enabled = true;
+
+  /**
+   * BasicShadowMap 能够给出没有经过过滤的阴影映射 —— 速度最快,但质量最差。
+   * PCFShadowMap 为默认值,使用Percentage-Closer Filtering (PCF)算法来过滤阴影映射。
+   * PCFSoftShadowMap 和PCFShadowMap一样使用 Percentage-Closer Filtering (PCF) 算法过滤阴影映射,但在使用低分辨率阴影图时具有更好的软阴影。
+   * VSMShadowMap 使用Variance Shadow Map (VSM)算法来过滤阴影映射。当使用VSMShadowMap时,所有阴影接收者也将会投射阴影
+   */
+  render.shadowMap.type = THREE.PCFSoftShadowMap;
+  /**
+   * 真实光照
+   */
+  render.physicallyCorrectLights = true;
+  /**
+   * THREE.LinearEncoding:线性颜色空间
+   * THREE.sRGBEncoding:sRGB (opens new window)颜色空间 这种更真实
+   */
+  render.outputColorSpace = THREE.SRGBColorSpace;
+
+  render.toneMapping = THREE.ACESFilmicToneMapping;
+
+  return render;
+}
+
+/**
+ * @type {THREE.Scene}
+ */
+const scene = new THREE.Scene();
+export function getScence() {
+  return scene;
+}
+
+/**
+ *
+ * @param  {...THREE.Mesh} args
+ */
+export function activeShadow(...args) {
+  args.forEach((mesh) => {
+    mesh.castShadow = true;
+    mesh.receiveShadow = true;
+  });
+}
+
+export function initCamera() {
+  const camera = new THREE.PerspectiveCamera(
+    50,
+    getCanvasSize().width / getCanvasSize().height,
+    0.1,
+    2000
+  );
+  camera.updateProjectionMatrix();
+  camera.position.set(40, 40, 40);
+  camera.lookAt(new THREE.Vector3(0, 0, 0));
+  return camera;
+}
+
+export function initAxes() {
+  const axes = new THREE.AxesHelper(100);
+  return axes;
+}
+
+export function initBoxMesh() {
+  return new THREE.Mesh(
+    new THREE.BoxGeometry(10, 10, 10, 5, 5, 5),
+    new THREE.MeshStandardMaterial({
+      color: 0xffffff,
+    })
+  );
+}
+
+export function initPlaneMesh() {
+  const mesh = new THREE.Mesh(
+    new THREE.PlaneGeometry(100, 100),
+    new THREE.MeshStandardMaterial({
+      color: 0x0000ff,
+      side: THREE.DoubleSide,
+    })
+  );
+
+  mesh.rotation.x = -Math.PI / 2;
+  return mesh;
+}
+
+const hemiLight = new THREE.HemisphereLight("#ffffff", "#000000", 1);
+scene.add(hemiLight);
+
+export function initHemiLight() {
+  return new THREE.HemisphereLight("#ffffff", "#000000", 1);
+}
+
+export function initLight() {
+  const light = new THREE.PointLight(0xffffff, 100);
+  light.castShadow = true;
+  light.position.set(50, 50, 0);
+  return light;
+}
+
+/**
+ *
+ * @param {THREE.Camera} camera
+ * @param {THREE.WebGLRenderer} render
+ */
+export function initControl(camera, render) {
+  const control = new OrbitControls(camera, render.domElement);
+  // 使动画循环使用时阻尼或自转 意思是否有惯性
+  control.enableDamping = true;
+  control.dampingFactor = 0.2;
+
+  control.enableZoom = true;
+  control.enableRotate = true;
+  control.autoRotate = false;
+  // 是否开启右键拖拽
+  control.enablePan = true;
+
+  // 设置相机距离原点的最远距离
+  control.minDistance = 10;
+  // 设置相机距离原点的最远距离
+  control.maxDistance = 300;
+
+  return control;
+}
+
+export function initStatus() {
+  return new Stats();
+}
+
+export function initGui() {
+  return new dat.GUI();
+}
+
+/**
+ *
+ * @param {THREE.Camera} camera
+ * @param {THREE.WebGLRenderer} render
+ */
+export function addResizeAdapter(camera, render) {
+  window.addEventListener("resize", () => {
+    camera.aspect = getCanvasSize().width / getCanvasSize().height;
+    camera.updateProjectionMatrix();
+    render.setSize(getCanvasSize().width, getCanvasSize().height);
+    render.render(scene, camera);
+  });
+}
+
+export function startAnimate(cb) {
+  function run() {
+    cb();
+    requestAnimationFrame(() => {
+      run();
+    });
+  }
+  run();
+}

+ 206 - 0
src/views/webgl/index.vue

@@ -0,0 +1,206 @@
+<template>
+  <div>
+    <canvas ref="canvas" id="canvas" width="500" height="500" />
+    <component
+      :is="'script'"
+      src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"
+    />
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+
+/**
+ * @type {import("vue").Ref<HTMLCanvasElement>}
+ */
+const canvas = ref();
+
+/**
+ * @param {WebGLRenderingContext} gl
+ * @param {number} type
+ * @param {string} source
+ */
+function createShader(gl, type, source) {
+  var shader = gl.createShader(type); // 创建着色器对象
+  gl.shaderSource(shader, source); // 提供数据源
+  gl.compileShader(shader); // 编译 -> 生成着色器
+  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+  if (success) {
+    return shader;
+  }
+
+  gl.deleteShader(shader);
+}
+
+/**
+ * @param {WebGLRenderingContext} gl
+ * @param {WebGLShader} vertexShader
+ * @param {WebGLShader} fragmentShader
+ */
+function createProgram(gl, vertexShader, fragmentShader) {
+  var program = gl.createProgram();
+  gl.attachShader(program, vertexShader);
+  gl.attachShader(program, fragmentShader);
+  gl.linkProgram(program);
+  var success = gl.getProgramParameter(program, gl.LINK_STATUS);
+  if (success) {
+    return program;
+  }
+
+  gl.deleteProgram(program);
+}
+
+// 返回 0 到 range 范围内的随机整数
+function randomInt(range) {
+  return Math.floor(Math.random() * range);
+}
+
+// 用参数生成矩形顶点并写进缓冲
+
+function setRectangle(gl, x, y, width, height) {
+  var x1 = x;
+  var x2 = x + width;
+  var y1 = y;
+  var y2 = y + height;
+
+  // 注意: gl.bufferData(gl.ARRAY_BUFFER, ...) 将会影响到
+  // 当前绑定点`ARRAY_BUFFER`的绑定缓冲
+  // 目前我们只有一个缓冲,如果我们有多个缓冲
+  // 我们需要先将所需缓冲绑定到`ARRAY_BUFFER`
+
+  gl.bufferData(
+    gl.ARRAY_BUFFER,
+    new Float32Array([x1, y1, x2, y1, x1, y2, x1, y2, x2, y1, x2, y2]),
+    gl.STATIC_DRAW
+  );
+}
+
+onMounted(() => {
+  const gl = canvas.value.getContext("webgl");
+
+  const vertexShader = createShader(
+    gl,
+    gl.VERTEX_SHADER,
+    `
+      // 一个属性变量,将会从缓冲中获取数据
+      attribute vec2 a_position;
+ 
+      uniform vec2 u_resolution;
+      varying vec4 v_color;
+    
+      void main() {
+        // 从像素坐标转换到 0.0 到 1.0
+        vec2 zeroToOne = a_position / u_resolution;
+    
+        // 再把 0->1 转换 0->2
+        vec2 zeroToTwo = zeroToOne * 2.0;
+    
+        // 把 0->2 转换到 -1->+1 (裁剪空间)
+        vec2 clipSpace = zeroToTwo - 1.0;
+    
+        // gl_Position = vec4(clipSpace, 0, 1);
+
+        gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
+
+        v_color = gl_Position * 0.5 + 0.5;
+
+      }
+    `
+  );
+  const fragmentShader = createShader(
+    gl,
+    gl.FRAGMENT_SHADER,
+    `
+      // 片段着色器没有默认精度,所以我们需要设置一个精度
+      // mediump是一个不错的默认值,代表“medium precision”(中等精度)
+      precision mediump float;
+
+      uniform vec4 u_color;
+
+      varying vec4 v_color;
+
+      void main() {
+        // gl_FragColor = u_color;
+        gl_FragColor = u_color;
+      }
+    `
+  );
+  var program = createProgram(gl, vertexShader, fragmentShader);
+  var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
+  var resolutionUniformLocation = gl.getUniformLocation(
+    program,
+    "u_resolution"
+  );
+
+  var positionBuffer = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+  // gl.bufferData(
+  //   gl.ARRAY_BUFFER,
+  //   new Float32Array([0, 0, 0, 0.5, 0.5, 0]),
+  //   gl.STATIC_DRAW
+  // );
+
+  gl.bufferData(
+    gl.ARRAY_BUFFER,
+    new Float32Array([0, 0, 0, 100, 100, 0, 500, 0, 500, 100, 400, 0]),
+    gl.STATIC_DRAW
+  );
+
+  gl.useProgram(program);
+  gl.enableVertexAttribArray(resolutionUniformLocation);
+  gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
+
+  window.webglUtils.resizeCanvasToDisplaySize(gl.canvas);
+  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+  gl.clearColor(0, 0, 0, 0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  var size = 2; // 每次迭代运行提取两个单位数据
+  var type = gl.FLOAT; // 每个单位的数据类型是32位浮点型
+  var normalize = false; // 不需要归一化数据
+  var stride = 0; // 0 = 移动单位数量 * 每个单位占用内存(sizeof(type)) 每次迭代运行运动多少内存到下一个数据开始点
+  var offset = 0; // 从缓冲起始位置开始读取
+  gl.vertexAttribPointer(
+    resolutionUniformLocation,
+    size,
+    type,
+    normalize,
+    stride,
+    offset
+  );
+
+  var primitiveType = gl.TRIANGLES;
+  gl.drawArrays(primitiveType, 0, 100);
+
+  var colorUniformLocation = gl.getUniformLocation(program, "u_color");
+
+  // 绘制50个随机颜色矩形
+  for (var ii = 0; ii < 50; ++ii) {
+    // 创建一个随机矩形
+    // 并将写入位置缓冲
+    // 因为位置缓冲是我们绑定在
+    // `ARRAY_BUFFER`绑定点上的最后一个缓冲
+    setRectangle(
+      gl,
+      randomInt(300),
+      randomInt(300),
+      randomInt(300),
+      randomInt(300)
+    );
+
+    // 设置一个随机颜色
+    gl.uniform4f(
+      colorUniformLocation,
+      Math.random(),
+      Math.random(),
+      Math.random(),
+      1
+    );
+
+    // 绘制矩形
+    gl.drawArrays(gl.TRIANGLES, 0, 6);
+  }
+});
+</script>

+ 2 - 1
tsconfig.json

@@ -11,7 +11,8 @@
     "skipLibCheck": true, 
     "importHelpers": true,     
     "forceConsistentCasingInFileNames": true,
-    "noUnusedLocals": true,
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
     "allowJs": true,
     "paths": {
       "tslib" : ["node_modules/tslib/tslib.d.ts"],

+ 69 - 0
yarn.lock

@@ -1290,6 +1290,11 @@
   resolved "https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
   integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
 
+"@tweenjs/tween.js@~18.6.4":
+  version "18.6.4"
+  resolved "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-18.6.4.tgz#40a3d0a93647124872dec8e0fd1bd5926695b6ca"
+  integrity sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==
+
 "@types/body-parser@*":
   version "1.19.2"
   resolved "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
@@ -1320,6 +1325,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/dat.gui@^0.7.10":
+  version "0.7.10"
+  resolved "https://registry.npmmirror.com/@types/dat.gui/-/dat.gui-0.7.10.tgz#e61b78c06ea5614397e8c2199fd0cd1afcd66675"
+  integrity sha512-sLifoNrvs4lgJkAUZcTB28dbWy0AM05Yc0BzTqHheK0tRYRVsJHoFLocB5heDbb5amaFLEXna41GOM6IG06oqA==
+
 "@types/eslint-scope@^3.7.3":
   version "3.7.4"
   resolved "https://registry.npmmirror.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
@@ -1464,6 +1474,28 @@
   dependencies:
     "@types/node" "*"
 
+"@types/stats.js@*":
+  version "0.17.0"
+  resolved "https://registry.npmmirror.com/@types/stats.js/-/stats.js-0.17.0.tgz#0ed81d48e03b590c24da85540c1d952077a9fe20"
+  integrity sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==
+
+"@types/three@^0.155.1":
+  version "0.155.1"
+  resolved "https://registry.npmmirror.com/@types/three/-/three-0.155.1.tgz#03e1cc9bf7132f50606254611ff5f68ccd682b05"
+  integrity sha512-uNUwnz/wWRxahjIqTtDYQ1qdE1R1py21obxfuILkT+kKrjocMwRLQQA1l8nMxfQU7VXb7CXu04ucMo8OqZt4ZA==
+  dependencies:
+    "@tweenjs/tween.js" "~18.6.4"
+    "@types/stats.js" "*"
+    "@types/webxr" "*"
+    fflate "~0.6.9"
+    lil-gui "~0.17.0"
+    meshoptimizer "~0.18.1"
+
+"@types/webxr@*":
+  version "0.5.4"
+  resolved "https://registry.npmmirror.com/@types/webxr/-/webxr-0.5.4.tgz#3d55a6427f9281d456843d754c99bf7804657fe3"
+  integrity sha512-41gfGLTtqXZhcmoDlLDHqMJDuwAMwhHwXf9Q2job3TUBsvkNfPNI/3IWVEtLH4tyY1ElWtfwIaoNeqeEX238/Q==
+
 "@types/ws@^8.5.5":
   version "8.5.5"
   resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb"
@@ -2930,6 +2962,11 @@ csstype@^3.1.1:
   resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
   integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
 
+dat.gui@^0.7.9:
+  version "0.7.9"
+  resolved "https://registry.npmmirror.com/dat.gui/-/dat.gui-0.7.9.tgz#860cab06053b028e327820eabdf25a13cf07b17e"
+  integrity sha512-sCNc1OHobc+Erc1HqiswYgHdVNpSJUlk/Hz8vzOCsER7rl+oF/4+v8GXFUyCgtXpoCX6+bnmg07DedLvBLwYKQ==
+
 debug@2.6.9:
   version "2.6.9"
   resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -3528,6 +3565,11 @@ faye-websocket@^0.11.3:
   dependencies:
     websocket-driver ">=0.5.1"
 
+fflate@~0.6.9:
+  version "0.6.10"
+  resolved "https://registry.npmmirror.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43"
+  integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==
+
 figures@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
@@ -4313,6 +4355,11 @@ levn@^0.4.1:
     prelude-ls "^1.2.1"
     type-check "~0.4.0"
 
+lil-gui@~0.17.0:
+  version "0.17.0"
+  resolved "https://registry.npmmirror.com/lil-gui/-/lil-gui-0.17.0.tgz#b41ae55d0023fcd9185f7395a218db0f58189663"
+  integrity sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==
+
 lilconfig@^2.0.3:
   version "2.1.0"
   resolved "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
@@ -4505,6 +4552,11 @@ merge2@^1.3.0, merge2@^1.4.1:
   resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
+meshoptimizer@~0.18.1:
+  version "0.18.1"
+  resolved "https://registry.npmmirror.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz#cdb90907f30a7b5b1190facd3b7ee6b7087797d8"
+  integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==
+
 methods@~1.1.2:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -6089,6 +6141,23 @@ thread-loader@^3.0.0:
     neo-async "^2.6.2"
     schema-utils "^3.0.0"
 
+three-addons@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/three-addons/-/three-addons-1.2.0.tgz#a36293e2489bdda08be8efc31291485b41de1560"
+  integrity sha512-tmPGLtcsDb0Bt6bVU82y9KX0rdAzizr7t2rFjBecWq4pvfGMY3l+yCfvntGYeZGJ9cjsli10cHSWHsN1pTy4mA==
+  dependencies:
+    three "^0.92.0"
+
+three@^0.156.1:
+  version "0.156.1"
+  resolved "https://registry.npmmirror.com/three/-/three-0.156.1.tgz#bab4fec121a5b3975eb4f4d227d9c912171eb399"
+  integrity sha512-kP7H0FK9d/k6t/XvQ9FO6i+QrePoDcNhwl0I02+wmUJRNSLCUIDMcfObnzQvxb37/0Uc9TDT0T1HgsRRrO6SYQ==
+
+three@^0.92.0:
+  version "0.92.0"
+  resolved "https://registry.npmmirror.com/three/-/three-0.92.0.tgz#8d3d1f5af890e62da7f4cb45d20c09fa51057dcd"
+  integrity sha512-yQzuwMDEyxc+TFHn8c+LtpK1Sqpy8I3FAr+1EoqqLmNmv6hjfFqB8PlYztRLuM4FMFb4hIbpUoP7Gc9bPQvkCg==
+
 thunky@^1.0.2:
   version "1.1.0"
   resolved "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio