@sliiu/present-three-bake 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COPYRIGHT +5 -0
- package/LICENSE +661 -0
- package/README.md +8 -2
- package/THIRD-PARTY-NOTICES.md +221 -0
- package/dist/cjs/bake-sp3d.js +176 -0
- package/dist/cjs/camera.js +94 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/light-rig.js +162 -0
- package/dist/cjs/material.js +136 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/path-to-shape.js +331 -0
- package/dist/cjs/presets.js +112 -0
- package/dist/cjs/resolve.js +98 -0
- package/dist/cjs/runtime.js +146 -0
- package/dist/cjs/scene-builder.js +329 -0
- package/dist/cjs/shape-path.js +71 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/bake-sp3d.js +140 -0
- package/dist/esm/camera.js +58 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/light-rig.js +126 -0
- package/dist/esm/material.js +98 -0
- package/dist/esm/path-to-shape.js +295 -0
- package/dist/esm/presets.js +109 -0
- package/dist/esm/resolve.js +91 -0
- package/dist/esm/runtime.js +141 -0
- package/dist/esm/scene-builder.js +290 -0
- package/dist/esm/shape-path.js +64 -0
- package/dist/esm/types.js +1 -0
- package/dist/types/bake-sp3d.d.ts +2 -0
- package/dist/types/camera.d.ts +7 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/light-rig.d.ts +10 -0
- package/dist/types/material.d.ts +17 -0
- package/dist/types/path-to-shape.d.ts +11 -0
- package/dist/types/presets.d.ts +23 -0
- package/dist/types/resolve.d.ts +67 -0
- package/dist/types/runtime.d.ts +22 -0
- package/dist/types/scene-builder.d.ts +35 -0
- package/dist/types/shape-path.d.ts +15 -0
- package/dist/types/types.d.ts +1 -0
- package/package.json +40 -4
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.applyLightRig = applyLightRig;
|
|
37
|
+
const THREE = __importStar(require("three"));
|
|
38
|
+
const RIG_PRESETS = {
|
|
39
|
+
threePt: {
|
|
40
|
+
ambient: 0.3,
|
|
41
|
+
lights: [
|
|
42
|
+
{ color: 0xffffff, intensity: 0.9, pos: [3, 4, 5] },
|
|
43
|
+
{ color: 0xffffff, intensity: 0.45, pos: [-4, 2, 3] },
|
|
44
|
+
{ color: 0xffffff, intensity: 0.25, pos: [0, -4, -3] },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
balanced: {
|
|
48
|
+
ambient: 0.45,
|
|
49
|
+
lights: [
|
|
50
|
+
{ color: 0xffffff, intensity: 0.7, pos: [3, 3, 4] },
|
|
51
|
+
{ color: 0xffffff, intensity: 0.5, pos: [-3, 3, 4] },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
soft: {
|
|
55
|
+
ambient: 0.55,
|
|
56
|
+
lights: [{ color: 0xffffff, intensity: 0.55, pos: [2, 4, 4] }],
|
|
57
|
+
},
|
|
58
|
+
harsh: {
|
|
59
|
+
ambient: 0.15,
|
|
60
|
+
lights: [{ color: 0xffffff, intensity: 1.4, pos: [4, 6, 4] }],
|
|
61
|
+
},
|
|
62
|
+
flood: {
|
|
63
|
+
ambient: 0.65,
|
|
64
|
+
lights: [
|
|
65
|
+
{ color: 0xffffff, intensity: 0.4, pos: [3, 0, 5] },
|
|
66
|
+
{ color: 0xffffff, intensity: 0.4, pos: [-3, 0, 5] },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
sunset: {
|
|
70
|
+
ambient: 0.3,
|
|
71
|
+
lights: [
|
|
72
|
+
{ color: 0xffd9a8, intensity: 1.0, pos: [4, 2, 3] },
|
|
73
|
+
{ color: 0x6a8fa6, intensity: 0.3, pos: [-3, 3, 4] },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
morning: {
|
|
77
|
+
ambient: 0.4,
|
|
78
|
+
lights: [
|
|
79
|
+
{ color: 0xfff1d6, intensity: 0.85, pos: [3, 5, 4] },
|
|
80
|
+
{ color: 0xffffff, intensity: 0.3, pos: [-3, 2, 2] },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
freezing: {
|
|
84
|
+
ambient: 0.4,
|
|
85
|
+
lights: [
|
|
86
|
+
{ color: 0xd6e7ff, intensity: 0.9, pos: [3, 4, 4] },
|
|
87
|
+
{ color: 0xffffff, intensity: 0.4, pos: [-3, 2, 3] },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
flat: {
|
|
91
|
+
ambient: 0.85,
|
|
92
|
+
lights: [{ color: 0xffffff, intensity: 0.2, pos: [0, 0, 5] }],
|
|
93
|
+
},
|
|
94
|
+
twoPt: {
|
|
95
|
+
ambient: 0.3,
|
|
96
|
+
lights: [
|
|
97
|
+
{ color: 0xffffff, intensity: 0.8, pos: [4, 3, 4] },
|
|
98
|
+
{ color: 0xffffff, intensity: 0.4, pos: [-4, -1, 3] },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
glow: {
|
|
102
|
+
ambient: 0.8,
|
|
103
|
+
lights: [{ color: 0xfff8d0, intensity: 0.4, pos: [0, 0, 6] }],
|
|
104
|
+
},
|
|
105
|
+
brightRoom: {
|
|
106
|
+
ambient: 0.7,
|
|
107
|
+
lights: [{ color: 0xffffff, intensity: 0.45, pos: [3, 4, 4] }],
|
|
108
|
+
},
|
|
109
|
+
chilly: {
|
|
110
|
+
ambient: 0.35,
|
|
111
|
+
lights: [
|
|
112
|
+
{ color: 0xb8d0ff, intensity: 0.9, pos: [3, 3, 4] },
|
|
113
|
+
{ color: 0xffffff, intensity: 0.3, pos: [-3, 1, 2] },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
contrasting: {
|
|
117
|
+
ambient: 0.2,
|
|
118
|
+
lights: [
|
|
119
|
+
{ color: 0xffffff, intensity: 1.2, pos: [4, 5, 4] },
|
|
120
|
+
{ color: 0x000000, intensity: 0, pos: [0, 0, 0] },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
function applyLightRig(scene, rigName, dir) {
|
|
125
|
+
const spec = RIG_PRESETS[rigName ?? "threePt"] ?? RIG_PRESETS.threePt;
|
|
126
|
+
const amb = new THREE.AmbientLight(0xffffff, spec.ambient);
|
|
127
|
+
scene.add(amb);
|
|
128
|
+
const yaw = dirToYaw(dir);
|
|
129
|
+
for (const l of spec.lights) {
|
|
130
|
+
const light = new THREE.DirectionalLight(l.color, l.intensity);
|
|
131
|
+
const [x, y, z] = rotateXZ(l.pos, yaw);
|
|
132
|
+
light.position.set(x, y, z);
|
|
133
|
+
scene.add(light);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function dirToYaw(dir) {
|
|
137
|
+
switch (dir) {
|
|
138
|
+
case "t":
|
|
139
|
+
return 0;
|
|
140
|
+
case "tr":
|
|
141
|
+
return Math.PI / 4;
|
|
142
|
+
case "r":
|
|
143
|
+
return Math.PI / 2;
|
|
144
|
+
case "br":
|
|
145
|
+
return (3 * Math.PI) / 4;
|
|
146
|
+
case "b":
|
|
147
|
+
return Math.PI;
|
|
148
|
+
case "bl":
|
|
149
|
+
return -3 * Math.PI / 4;
|
|
150
|
+
case "l":
|
|
151
|
+
return -Math.PI / 2;
|
|
152
|
+
case "tl":
|
|
153
|
+
return -Math.PI / 4;
|
|
154
|
+
default:
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function rotateXZ(p, yaw) {
|
|
159
|
+
const cos = Math.cos(yaw);
|
|
160
|
+
const sin = Math.sin(yaw);
|
|
161
|
+
return [p[0] * cos - p[2] * sin, p[1], p[0] * sin + p[2] * cos];
|
|
162
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.pickPrimaryColor = pickPrimaryColor;
|
|
37
|
+
exports.buildMaterial = buildMaterial;
|
|
38
|
+
exports.applyTextureCrop = applyTextureCrop;
|
|
39
|
+
const THREE = __importStar(require("three"));
|
|
40
|
+
const MATERIAL_PRESETS = {
|
|
41
|
+
legacyMatte: { metalness: 0, roughness: 1 },
|
|
42
|
+
legacyPlastic: { metalness: 0, roughness: 0.4 },
|
|
43
|
+
legacyMetal: { metalness: 0.9, roughness: 0.35 },
|
|
44
|
+
legacyWireframe: { metalness: 0, roughness: 1 },
|
|
45
|
+
matte: { metalness: 0, roughness: 0.95 },
|
|
46
|
+
plastic: { metalness: 0, roughness: 0.35 },
|
|
47
|
+
metal: { metalness: 0.85, roughness: 0.3 },
|
|
48
|
+
warmMatte: { metalness: 0, roughness: 0.95 },
|
|
49
|
+
translucentPowder: { metalness: 0, roughness: 0.6 },
|
|
50
|
+
powder: { metalness: 0, roughness: 0.8 },
|
|
51
|
+
dkEdge: { metalness: 0, roughness: 0.6 },
|
|
52
|
+
softEdge: { metalness: 0, roughness: 0.5 },
|
|
53
|
+
clear: { metalness: 0, roughness: 0.05, clearcoat: 1 },
|
|
54
|
+
flat: { metalness: 0, roughness: 1 },
|
|
55
|
+
softmetal: { metalness: 0.6, roughness: 0.5 },
|
|
56
|
+
};
|
|
57
|
+
function pickPrimaryColor(paint, fallback) {
|
|
58
|
+
if (!paint)
|
|
59
|
+
return fallback;
|
|
60
|
+
if (paint.kind === "solid")
|
|
61
|
+
return paint.color;
|
|
62
|
+
if (paint.kind === "linear" || paint.kind === "radial") {
|
|
63
|
+
const stops = paint.stops;
|
|
64
|
+
if (stops.length === 0)
|
|
65
|
+
return fallback;
|
|
66
|
+
const mid = stops[Math.floor(stops.length / 2)];
|
|
67
|
+
return mid?.color ?? stops[0]?.color ?? fallback;
|
|
68
|
+
}
|
|
69
|
+
return fallback;
|
|
70
|
+
}
|
|
71
|
+
function buildMaterial(paint, opts = {}) {
|
|
72
|
+
const fallback = opts.fallbackColor ?? "#7f7f7f";
|
|
73
|
+
const color = pickPrimaryColor(paint, fallback);
|
|
74
|
+
// ECMA-376 §20.1.5.12 sp3d/@prstMaterial 默认是 `warmMatte` —— 缺省 prstMaterial
|
|
75
|
+
// 时之前回落到 `plastic`,体高光太亮,看起来像塑料板而不是 PPT 默认的暖哑光纸/卡片
|
|
76
|
+
// 质感(用户在 WPS 里看到 "材料:暖色粗糙" = warmMatte)。
|
|
77
|
+
const preset = MATERIAL_PRESETS[opts.prst ?? "warmMatte"] ?? MATERIAL_PRESETS.warmMatte;
|
|
78
|
+
const useTexture = !!opts.imageTexture;
|
|
79
|
+
// image 贴图:lighting 有时会把 hi-contrast 黑白图(比如棋盘)压成灰白灰黑——
|
|
80
|
+
// PBR diffuse 永远把入射光按 albedo/π 漫反射回去,多光源叠加之后白只能勉强达
|
|
81
|
+
// 1.0、黑被 ambient 提到非零灰。视觉上的"暖色卡片纸 + 倒角高光"在没有 texture
|
|
82
|
+
// 的纯色面上才有用,有 texture 时 PowerPoint / WPS 表现是"贴图原色直接透出,
|
|
83
|
+
// 仅做轻微 shading"。
|
|
84
|
+
// → 有 texture 时切到 MeshBasicMaterial:完全不参与 lighting,texture 像素直
|
|
85
|
+
// 接 round-trip 出去,再配合 Three.js outputColorSpace=SRGB 就能 1:1 还原原图
|
|
86
|
+
// 的黑白对比度。lightRig 仍会落到几何,但只通过倒角/侧面(无贴图区)产生明暗
|
|
87
|
+
// 差,用户感知就是"贴图清晰 + 边缘有 3D 立体感"。
|
|
88
|
+
if (useTexture) {
|
|
89
|
+
if (opts.litTexture) {
|
|
90
|
+
// 折边 / 侧壁:贴图照常透出,但走 PBR 受光 + tint 压暗——正面(basic、满亮)与
|
|
91
|
+
// 折边之间形成稳定可辨的明暗折痕,区分「上面 / 前面」。roughness 取 1 让过渡柔和。
|
|
92
|
+
return new THREE.MeshStandardMaterial({
|
|
93
|
+
color: opts.tint ?? 0xffffff,
|
|
94
|
+
side: THREE.DoubleSide,
|
|
95
|
+
map: opts.imageTexture ?? null,
|
|
96
|
+
metalness: 0,
|
|
97
|
+
roughness: 1,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return new THREE.MeshBasicMaterial({
|
|
101
|
+
color: opts.tint ?? 0xffffff,
|
|
102
|
+
side: THREE.DoubleSide,
|
|
103
|
+
map: opts.imageTexture ?? null,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (preset.clearcoat !== undefined) {
|
|
107
|
+
const m = new THREE.MeshPhysicalMaterial({
|
|
108
|
+
color,
|
|
109
|
+
metalness: preset.metalness,
|
|
110
|
+
roughness: preset.roughness,
|
|
111
|
+
clearcoat: preset.clearcoat,
|
|
112
|
+
side: THREE.DoubleSide,
|
|
113
|
+
});
|
|
114
|
+
return m;
|
|
115
|
+
}
|
|
116
|
+
const m = new THREE.MeshStandardMaterial({
|
|
117
|
+
color,
|
|
118
|
+
metalness: preset.metalness,
|
|
119
|
+
roughness: preset.roughness,
|
|
120
|
+
side: THREE.DoubleSide,
|
|
121
|
+
});
|
|
122
|
+
return m;
|
|
123
|
+
}
|
|
124
|
+
// `<a:blip>` 上的 srcRect 把 0..1 的图像截到 (l, t, 1-r, 1-b) 区域。three.js
|
|
125
|
+
// 直接拿原图 + 调整 texture.offset / repeat 就等价裁剪——比预先把 imageData
|
|
126
|
+
// 切片成新 PNG 省一次 putImageData。
|
|
127
|
+
function applyTextureCrop(texture, cropRect) {
|
|
128
|
+
if (!cropRect)
|
|
129
|
+
return;
|
|
130
|
+
const { l, t, r, b } = cropRect;
|
|
131
|
+
const w = Math.max(1e-6, 1 - l - r);
|
|
132
|
+
const h = Math.max(1e-6, 1 - t - b);
|
|
133
|
+
texture.offset.set(l, b);
|
|
134
|
+
texture.repeat.set(w, h);
|
|
135
|
+
texture.needsUpdate = true;
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.pathDToShapes = pathDToShapes;
|
|
37
|
+
const THREE = __importStar(require("three"));
|
|
38
|
+
function pathDToShapes(d) {
|
|
39
|
+
const out = [];
|
|
40
|
+
let current = null;
|
|
41
|
+
let x = 0;
|
|
42
|
+
let y = 0;
|
|
43
|
+
let startX = 0;
|
|
44
|
+
let startY = 0;
|
|
45
|
+
let cpx = 0;
|
|
46
|
+
let cpy = 0;
|
|
47
|
+
let lastCmd = "";
|
|
48
|
+
const minMax = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
|
|
49
|
+
const track = (px, py) => {
|
|
50
|
+
if (px < minMax.minX)
|
|
51
|
+
minMax.minX = px;
|
|
52
|
+
if (py < minMax.minY)
|
|
53
|
+
minMax.minY = py;
|
|
54
|
+
if (px > minMax.maxX)
|
|
55
|
+
minMax.maxX = px;
|
|
56
|
+
if (py > minMax.maxY)
|
|
57
|
+
minMax.maxY = py;
|
|
58
|
+
};
|
|
59
|
+
const tokens = tokenizePath(d);
|
|
60
|
+
let i = 0;
|
|
61
|
+
while (i < tokens.length) {
|
|
62
|
+
const t = tokens[i++];
|
|
63
|
+
if (typeof t !== "string")
|
|
64
|
+
continue;
|
|
65
|
+
const cmd = t;
|
|
66
|
+
const lower = cmd.toLowerCase();
|
|
67
|
+
const isRel = cmd !== cmd.toUpperCase();
|
|
68
|
+
const read = () => {
|
|
69
|
+
const v = tokens[i++];
|
|
70
|
+
return typeof v === "number" ? v : 0;
|
|
71
|
+
};
|
|
72
|
+
if (lower === "m") {
|
|
73
|
+
const nx = read() + (isRel ? x : 0);
|
|
74
|
+
const ny = read() + (isRel ? y : 0);
|
|
75
|
+
if (current)
|
|
76
|
+
out.push(current);
|
|
77
|
+
current = new THREE.Shape();
|
|
78
|
+
current.moveTo(nx, ny);
|
|
79
|
+
x = nx;
|
|
80
|
+
y = ny;
|
|
81
|
+
startX = nx;
|
|
82
|
+
startY = ny;
|
|
83
|
+
track(x, y);
|
|
84
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
85
|
+
const lx = read() + (isRel ? x : 0);
|
|
86
|
+
const ly = read() + (isRel ? y : 0);
|
|
87
|
+
current.lineTo(lx, ly);
|
|
88
|
+
x = lx;
|
|
89
|
+
y = ly;
|
|
90
|
+
track(x, y);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (lower === "l") {
|
|
94
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
95
|
+
const lx = read() + (isRel ? x : 0);
|
|
96
|
+
const ly = read() + (isRel ? y : 0);
|
|
97
|
+
current?.lineTo(lx, ly);
|
|
98
|
+
x = lx;
|
|
99
|
+
y = ly;
|
|
100
|
+
track(x, y);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (lower === "h") {
|
|
104
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
105
|
+
const lx = read() + (isRel ? x : 0);
|
|
106
|
+
current?.lineTo(lx, y);
|
|
107
|
+
x = lx;
|
|
108
|
+
track(x, y);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (lower === "v") {
|
|
112
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
113
|
+
const ly = read() + (isRel ? y : 0);
|
|
114
|
+
current?.lineTo(x, ly);
|
|
115
|
+
y = ly;
|
|
116
|
+
track(x, y);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (lower === "c") {
|
|
120
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
121
|
+
const c1x = read() + (isRel ? x : 0);
|
|
122
|
+
const c1y = read() + (isRel ? y : 0);
|
|
123
|
+
const c2x = read() + (isRel ? x : 0);
|
|
124
|
+
const c2y = read() + (isRel ? y : 0);
|
|
125
|
+
const ex = read() + (isRel ? x : 0);
|
|
126
|
+
const ey = read() + (isRel ? y : 0);
|
|
127
|
+
current?.bezierCurveTo(c1x, c1y, c2x, c2y, ex, ey);
|
|
128
|
+
cpx = c2x;
|
|
129
|
+
cpy = c2y;
|
|
130
|
+
x = ex;
|
|
131
|
+
y = ey;
|
|
132
|
+
track(x, y);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (lower === "s") {
|
|
136
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
137
|
+
const c1x = lastCmd === "c" || lastCmd === "s" ? 2 * x - cpx : x;
|
|
138
|
+
const c1y = lastCmd === "c" || lastCmd === "s" ? 2 * y - cpy : y;
|
|
139
|
+
const c2x = read() + (isRel ? x : 0);
|
|
140
|
+
const c2y = read() + (isRel ? y : 0);
|
|
141
|
+
const ex = read() + (isRel ? x : 0);
|
|
142
|
+
const ey = read() + (isRel ? y : 0);
|
|
143
|
+
current?.bezierCurveTo(c1x, c1y, c2x, c2y, ex, ey);
|
|
144
|
+
cpx = c2x;
|
|
145
|
+
cpy = c2y;
|
|
146
|
+
x = ex;
|
|
147
|
+
y = ey;
|
|
148
|
+
track(x, y);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else if (lower === "q") {
|
|
152
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
153
|
+
const c1x = read() + (isRel ? x : 0);
|
|
154
|
+
const c1y = read() + (isRel ? y : 0);
|
|
155
|
+
const ex = read() + (isRel ? x : 0);
|
|
156
|
+
const ey = read() + (isRel ? y : 0);
|
|
157
|
+
current?.quadraticCurveTo(c1x, c1y, ex, ey);
|
|
158
|
+
cpx = c1x;
|
|
159
|
+
cpy = c1y;
|
|
160
|
+
x = ex;
|
|
161
|
+
y = ey;
|
|
162
|
+
track(x, y);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (lower === "t") {
|
|
166
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
167
|
+
const c1x = lastCmd === "q" || lastCmd === "t" ? 2 * x - cpx : x;
|
|
168
|
+
const c1y = lastCmd === "q" || lastCmd === "t" ? 2 * y - cpy : y;
|
|
169
|
+
const ex = read() + (isRel ? x : 0);
|
|
170
|
+
const ey = read() + (isRel ? y : 0);
|
|
171
|
+
current?.quadraticCurveTo(c1x, c1y, ex, ey);
|
|
172
|
+
cpx = c1x;
|
|
173
|
+
cpy = c1y;
|
|
174
|
+
x = ex;
|
|
175
|
+
y = ey;
|
|
176
|
+
track(x, y);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (lower === "a") {
|
|
180
|
+
while (i < tokens.length && typeof tokens[i] === "number") {
|
|
181
|
+
const rx = Math.abs(read());
|
|
182
|
+
const ry = Math.abs(read());
|
|
183
|
+
const phi = read();
|
|
184
|
+
const large = read();
|
|
185
|
+
const sweep = read();
|
|
186
|
+
const ex = read() + (isRel ? x : 0);
|
|
187
|
+
const ey = read() + (isRel ? y : 0);
|
|
188
|
+
const curves = svgArcToCubics(x, y, rx, ry, phi, large, sweep, ex, ey);
|
|
189
|
+
if (!current || curves.length === 0) {
|
|
190
|
+
current?.lineTo(ex, ey);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
for (const [c1x, c1y, c2x, c2y, px, py] of curves) {
|
|
194
|
+
current.bezierCurveTo(c1x, c1y, c2x, c2y, px, py);
|
|
195
|
+
track(c1x, c1y);
|
|
196
|
+
track(c2x, c2y);
|
|
197
|
+
track(px, py);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
x = ex;
|
|
201
|
+
y = ey;
|
|
202
|
+
track(x, y);
|
|
203
|
+
cpx = x;
|
|
204
|
+
cpy = y;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (lower === "z") {
|
|
208
|
+
current?.lineTo(startX, startY);
|
|
209
|
+
x = startX;
|
|
210
|
+
y = startY;
|
|
211
|
+
}
|
|
212
|
+
lastCmd = lower;
|
|
213
|
+
}
|
|
214
|
+
if (current)
|
|
215
|
+
out.push(current);
|
|
216
|
+
if (!Number.isFinite(minMax.minX)) {
|
|
217
|
+
minMax.minX = 0;
|
|
218
|
+
minMax.minY = 0;
|
|
219
|
+
minMax.maxX = 0;
|
|
220
|
+
minMax.maxY = 0;
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
shapes: out,
|
|
224
|
+
bbox: minMax,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function tokenizePath(d) {
|
|
228
|
+
const out = [];
|
|
229
|
+
let i = 0;
|
|
230
|
+
while (i < d.length) {
|
|
231
|
+
const c = d[i];
|
|
232
|
+
if (/[a-zA-Z]/.test(c)) {
|
|
233
|
+
out.push(c);
|
|
234
|
+
i++;
|
|
235
|
+
}
|
|
236
|
+
else if (/[\s,]/.test(c)) {
|
|
237
|
+
i++;
|
|
238
|
+
}
|
|
239
|
+
else if (c === "-" || c === "+" || c === "." || /\d/.test(c)) {
|
|
240
|
+
let j = i;
|
|
241
|
+
if (d[j] === "-" || d[j] === "+")
|
|
242
|
+
j++;
|
|
243
|
+
while (j < d.length && /[0-9.]/.test(d[j]))
|
|
244
|
+
j++;
|
|
245
|
+
if (d[j] === "e" || d[j] === "E") {
|
|
246
|
+
j++;
|
|
247
|
+
if (d[j] === "-" || d[j] === "+")
|
|
248
|
+
j++;
|
|
249
|
+
while (j < d.length && /\d/.test(d[j]))
|
|
250
|
+
j++;
|
|
251
|
+
}
|
|
252
|
+
const num = Number(d.slice(i, j));
|
|
253
|
+
if (Number.isFinite(num))
|
|
254
|
+
out.push(num);
|
|
255
|
+
i = j > i ? j : i + 1;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
i++;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
// SVG arc → 三次贝塞尔。端点参数按 SVG 1.1 附录 F.6 转成圆心,再按不超过 90° 分段。
|
|
264
|
+
function svgArcToCubics(x1, y1, rx, ry, phiDeg, large, sweep, x2, y2) {
|
|
265
|
+
if (rx === 0 || ry === 0)
|
|
266
|
+
return [];
|
|
267
|
+
if (x1 === x2 && y1 === y2)
|
|
268
|
+
return [];
|
|
269
|
+
const phi = (phiDeg * Math.PI) / 180;
|
|
270
|
+
const sinphi = Math.sin(phi);
|
|
271
|
+
const cosphi = Math.cos(phi);
|
|
272
|
+
const dx = (x1 - x2) / 2;
|
|
273
|
+
const dy = (y1 - y2) / 2;
|
|
274
|
+
const px = cosphi * dx + sinphi * dy;
|
|
275
|
+
const py = -sinphi * dx + cosphi * dy;
|
|
276
|
+
rx = Math.abs(rx);
|
|
277
|
+
ry = Math.abs(ry);
|
|
278
|
+
const px2 = px * px;
|
|
279
|
+
const py2 = py * py;
|
|
280
|
+
let rx2 = rx * rx;
|
|
281
|
+
let ry2 = ry * ry;
|
|
282
|
+
const lam = px2 / rx2 + py2 / ry2;
|
|
283
|
+
if (lam > 1) {
|
|
284
|
+
const s = Math.sqrt(lam);
|
|
285
|
+
rx *= s;
|
|
286
|
+
ry *= s;
|
|
287
|
+
rx2 = rx * rx;
|
|
288
|
+
ry2 = ry * ry;
|
|
289
|
+
}
|
|
290
|
+
const sign = large === sweep ? -1 : 1;
|
|
291
|
+
let sq = (rx2 * ry2 - rx2 * py2 - ry2 * px2) / (rx2 * py2 + ry2 * px2);
|
|
292
|
+
if (!Number.isFinite(sq) || sq < 0)
|
|
293
|
+
sq = 0;
|
|
294
|
+
const coef = sign * Math.sqrt(sq);
|
|
295
|
+
const cxp = (coef * rx * py) / ry;
|
|
296
|
+
const cyp = (coef * -ry * px) / rx;
|
|
297
|
+
const cx = cosphi * cxp - sinphi * cyp + (x1 + x2) / 2;
|
|
298
|
+
const cy = sinphi * cxp + cosphi * cyp + (y1 + y2) / 2;
|
|
299
|
+
const theta1 = vectorAngle(1, 0, (px - cxp) / rx, (py - cyp) / ry);
|
|
300
|
+
let dtheta = vectorAngle((px - cxp) / rx, (py - cyp) / ry, (-px - cxp) / rx, (-py - cyp) / ry);
|
|
301
|
+
if (!sweep && dtheta > 0)
|
|
302
|
+
dtheta -= Math.PI * 2;
|
|
303
|
+
if (sweep && dtheta < 0)
|
|
304
|
+
dtheta += Math.PI * 2;
|
|
305
|
+
const segs = Math.max(1, Math.ceil(Math.abs(dtheta) / (Math.PI / 2)));
|
|
306
|
+
const delta = dtheta / segs;
|
|
307
|
+
const t = (4 / 3) * Math.tan(delta / 4);
|
|
308
|
+
const out = [];
|
|
309
|
+
const map = (lx, ly) => [
|
|
310
|
+
cosphi * lx - sinphi * ly + cx,
|
|
311
|
+
sinphi * lx + cosphi * ly + cy,
|
|
312
|
+
];
|
|
313
|
+
for (let i = 0; i < segs; i++) {
|
|
314
|
+
const th = theta1 + i * delta;
|
|
315
|
+
const th2 = th + delta;
|
|
316
|
+
const cosTh = Math.cos(th);
|
|
317
|
+
const sinTh = Math.sin(th);
|
|
318
|
+
const cosTh2 = Math.cos(th2);
|
|
319
|
+
const sinTh2 = Math.sin(th2);
|
|
320
|
+
const p1 = map(rx * (cosTh - t * sinTh), ry * (sinTh + t * cosTh));
|
|
321
|
+
const p2 = map(rx * (cosTh2 + t * sinTh2), ry * (sinTh2 - t * cosTh2));
|
|
322
|
+
const end = map(rx * cosTh2, ry * sinTh2);
|
|
323
|
+
out.push([p1[0], p1[1], p2[0], p2[1], end[0], end[1]]);
|
|
324
|
+
}
|
|
325
|
+
return out;
|
|
326
|
+
}
|
|
327
|
+
function vectorAngle(ux, uy, vx, vy) {
|
|
328
|
+
const dot = ux * vx + uy * vy;
|
|
329
|
+
const det = ux * vy - uy * vx;
|
|
330
|
+
return Math.atan2(det, dot);
|
|
331
|
+
}
|