three-video-projection 0.0.9 → 0.1.0

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/README.md CHANGED
@@ -37,12 +37,6 @@ git clone https://github.com/hh-hang/three-video-projection.git
37
37
  # 安装依赖
38
38
  npm install
39
39
 
40
- # 进入示例目录
41
- cd example
42
-
43
- # 安装示例依赖
44
- npm install
45
-
46
40
  # 运行开发服务器
47
41
  npm run dev
48
42
  ```
@@ -88,6 +82,8 @@ const projector = await createVideoProjector({
88
82
  opacity: 1.0, // 投影透明度
89
83
  projBias: 0.0001, // 深度偏移
90
84
  edgeFeather: 0.05, // 边缘羽化程度
85
+ cropRect: [0, 0, 1, 1], // 裁剪区域(UV空间,[x0, y0, x1, y1],范围 0~1)
86
+ quadCorners: [[0, 0], [1, 0], [1, 1], [0, 1]], // 四角点变换(投影UV空间,顺序:左下、右下、右上、左上)
91
87
  isShowHelper: true, // 是否显示相机辅助器
92
88
  });
93
89
 
@@ -126,6 +122,8 @@ projector.dispose();
126
122
  - `opacity?: number` — 全局透明度,默认 `1.0`。
127
123
  - `projBias?: number` — 深度偏移,默认 `0.0001`。
128
124
  - `edgeFeather?: number` — 边缘羽化宽度,默认 `0.05`。
125
+ - `cropRect?: [number, number, number, number]` — 视频纹理裁剪区域,UV 空间 `[x0, y0, x1, y1]`,范围 `0~1`。默认 `[0, 0, 1, 1]`(不裁剪)。
126
+ - `quadCorners?: [[number, number], [number, number], [number, number], [number, number]]` — 四角点变换,在投影 UV 空间中指定四角坐标(顺序:左下、右下、右上、左上),用于梯形/透视校正。默认为单位正方形。
129
127
  - `isShowHelper?: boolean` — 是否显示 `CameraHelper` 来可视化投影相机,默认 `true`。
130
128
 
131
129
  ---
@@ -142,10 +140,12 @@ projector.dispose();
142
140
  - `updateElevationDeg(deg: number): void` — 设置俯仰角(度)。
143
141
  - `updateRollDeg(deg: number): void` — 设置滚转角(度)。
144
142
  - `updateOpacity(opacity: number): void` — 更新投影透明度(0~1)。
143
+ - `updateCropRect(rect: [number, number, number, number]): void` — 动态更新裁剪区域(UV 空间,`[x0, y0, x1, y1]`)。
144
+ - `updateQuadCorners(corners: [[number, number], [number, number], [number, number], [number, number]]): void` — 动态更新四角点变换(投影 UV 空间,顺序:左下、右下、右上、左上)。
145
145
 
146
146
  属性:
147
147
 
148
- - `uniforms` — 暴露给外部的着色器 uniform 对象(包含 `projectorMap`、`projectorDepthMap`、`projectorMatrix`、`intensity`、`projBias`、`edgeFeather`、`opacity` 等)。
148
+ - `uniforms` — 暴露给外部的着色器 uniform 对象(包含 `projectorMap`、`projectorDepthMap`、`projectorMatrix`、`intensity`、`projBias`、`edgeFeather`、`opacity`、`cropRect`、`quadHomography` 等)。
149
149
  - `overlays: THREE.Mesh[]` — 内部创建的 overlay 列表(投影用透明网格)。
150
150
  - `targetMeshes: THREE.Mesh[]` — 当前被投影的目标网格列表。
151
151
  - `projCam: THREE.PerspectiveCamera` — 用于投影的相机。
package/dist/index.d.mts CHANGED
@@ -21,6 +21,8 @@ type ProjectorToolOptions = {
21
21
  opacity?: number;
22
22
  projBias?: number;
23
23
  edgeFeather?: number;
24
+ cropRect?: [number, number, number, number];
25
+ quadCorners?: [[number, number], [number, number], [number, number], [number, number]];
24
26
  isShowHelper?: boolean;
25
27
  };
26
28
  type ProjectorTool = {
@@ -32,6 +34,8 @@ type ProjectorTool = {
32
34
  updateElevationDeg: (deg: number) => void;
33
35
  updateRollDeg: (deg: number) => void;
34
36
  updateOpacity: (opacity: number) => void;
37
+ updateCropRect: (rect: [number, number, number, number]) => void;
38
+ updateQuadCorners: (corners: [[number, number], [number, number], [number, number], [number, number]]) => void;
35
39
  uniforms: any;
36
40
  overlays: THREE.Mesh[];
37
41
  targetMeshes: THREE.Mesh[];
package/dist/index.d.ts CHANGED
@@ -21,6 +21,8 @@ type ProjectorToolOptions = {
21
21
  opacity?: number;
22
22
  projBias?: number;
23
23
  edgeFeather?: number;
24
+ cropRect?: [number, number, number, number];
25
+ quadCorners?: [[number, number], [number, number], [number, number], [number, number]];
24
26
  isShowHelper?: boolean;
25
27
  };
26
28
  type ProjectorTool = {
@@ -32,6 +34,8 @@ type ProjectorTool = {
32
34
  updateElevationDeg: (deg: number) => void;
33
35
  updateRollDeg: (deg: number) => void;
34
36
  updateOpacity: (opacity: number) => void;
37
+ updateCropRect: (rect: [number, number, number, number]) => void;
38
+ updateQuadCorners: (corners: [[number, number], [number, number], [number, number], [number, number]]) => void;
35
39
  uniforms: any;
36
40
  overlays: THREE.Mesh[];
37
41
  targetMeshes: THREE.Mesh[];
package/dist/index.js CHANGED
@@ -35,6 +35,32 @@ module.exports = __toCommonJS(index_exports);
35
35
 
36
36
  // src/three-video-projection.ts
37
37
  var THREE = __toESM(require("three"));
38
+ function computeQuadHomography(corners) {
39
+ const [[x0, y0], [x1, y1], [x2, y2], [x3, y3]] = corners;
40
+ const dx1 = x1 - x2, dy1 = y1 - y2;
41
+ const dx2 = x3 - x2, dy2 = y3 - y2;
42
+ const dx3 = x0 - x1 + x2 - x3, dy3 = y0 - y1 + y2 - y3;
43
+ const den = dx1 * dy2 - dx2 * dy1;
44
+ const pg = (dx3 * dy2 - dx2 * dy3) / den;
45
+ const ph = (dx1 * dy3 - dx3 * dy1) / den;
46
+ const m00 = x1 - x0 + pg * x1, m01 = x3 - x0 + ph * x3, m02 = x0;
47
+ const m10 = y1 - y0 + pg * y1, m11 = y3 - y0 + ph * y3, m12 = y0;
48
+ const m20 = pg, m21 = ph, m22 = 1;
49
+ const det = m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20);
50
+ const inv = new THREE.Matrix3();
51
+ inv.set(
52
+ (m11 * m22 - m12 * m21) / det,
53
+ -(m01 * m22 - m02 * m21) / det,
54
+ (m01 * m12 - m02 * m11) / det,
55
+ -(m10 * m22 - m12 * m20) / det,
56
+ (m00 * m22 - m02 * m20) / det,
57
+ -(m00 * m12 - m02 * m10) / det,
58
+ (m10 * m21 - m11 * m20) / det,
59
+ -(m00 * m21 - m01 * m20) / det,
60
+ (m00 * m11 - m01 * m10) / det
61
+ );
62
+ return inv;
63
+ }
38
64
  async function createVideoProjector(opts) {
39
65
  const {
40
66
  scene,
@@ -48,6 +74,8 @@ async function createVideoProjector(opts) {
48
74
  opacity = 1,
49
75
  projBias = 1e-4,
50
76
  edgeFeather = 0.05,
77
+ cropRect = [0, 0, 1, 1],
78
+ quadCorners = [[0, 0], [1, 0], [1, 1], [0, 1]],
51
79
  isShowHelper = true
52
80
  } = opts;
53
81
  let orientParams = {
@@ -84,7 +112,9 @@ async function createVideoProjector(opts) {
84
112
  projectorDepthMap: { value: null },
85
113
  projBias: { value: projBias },
86
114
  edgeFeather: { value: edgeFeather },
87
- opacity: { value: opacity }
115
+ opacity: { value: opacity },
116
+ cropRect: { value: new THREE.Vector4(cropRect[0], cropRect[1], cropRect[2], cropRect[3]) },
117
+ quadHomography: { value: computeQuadHomography(quadCorners) }
88
118
  };
89
119
  const vertexShader = `
90
120
  varying vec3 vWorldPos;
@@ -104,6 +134,8 @@ async function createVideoProjector(opts) {
104
134
  uniform float projBias;
105
135
  uniform float edgeFeather;
106
136
  uniform float opacity;
137
+ uniform vec4 cropRect;
138
+ uniform mat3 quadHomography;
107
139
  varying vec3 vWorldPos;
108
140
  varying vec3 vWorldNormal;
109
141
 
@@ -112,8 +144,7 @@ async function createVideoProjector(opts) {
112
144
  if (projPos.w <= 0.0) discard;
113
145
  vec2 uv = projPos.xy / projPos.w * 0.5 + 0.5;
114
146
  if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) discard;
115
- vec4 color = texture(projectorMap, uv);
116
-
147
+
117
148
  // \u906E\u6321\u5254\u9664
118
149
  float projNDCz = projPos.z / projPos.w;
119
150
  float projDepth01 = projNDCz * 0.5 + 0.5;
@@ -122,13 +153,22 @@ async function createVideoProjector(opts) {
122
153
  discard;
123
154
  }
124
155
 
125
- // \u8FB9\u7F18\u7FBD\u5316
126
- vec2 adjUV = uv;
127
- float minDist = min(min(adjUV.x, 1.0 - adjUV.x), min(adjUV.y, 1.0 - adjUV.y));
156
+ // \u56DB\u89D2\u70B9\u53D8\u6362\uFF08\u6295\u5F71UV \u2192 \u89C6\u9891\u7EB9\u7406UV\uFF09
157
+ vec3 hw = quadHomography * vec3(uv, 1.0);
158
+ vec2 warpedUV = hw.xy / hw.z;
159
+
160
+ // \u81EA\u5B9A\u4E49\u88C1\u526A\u53CA\u7FBD\u5316
161
+ vec2 cropMin = cropRect.xy;
162
+ vec2 cropMax = cropRect.zw;
163
+ if (warpedUV.x < cropMin.x || warpedUV.x > cropMax.x || warpedUV.y < cropMin.y || warpedUV.y > cropMax.y) discard;
164
+ float distX = min(warpedUV.x - cropMin.x, cropMax.x - warpedUV.x);
165
+ float distY = min(warpedUV.y - cropMin.y, cropMax.y - warpedUV.y);
166
+ float minDist = min(distX, distY);
128
167
  float edgeFactor = 1.0;
129
168
  if (edgeFeather > 0.0) {
130
169
  edgeFactor = smoothstep(0.0, edgeFeather, minDist);
131
170
  }
171
+ vec4 color = texture(projectorMap, warpedUV);
132
172
  float effectiveAlpha = color.a * edgeFactor;
133
173
 
134
174
  // \u8F93\u51FA
@@ -281,6 +321,12 @@ async function createVideoProjector(opts) {
281
321
  const clamped = Math.max(0, Math.min(1, v));
282
322
  projectorUniforms.opacity.value = clamped;
283
323
  }
324
+ function updateCropRect(rect) {
325
+ projectorUniforms.cropRect.value.set(rect[0], rect[1], rect[2], rect[3]);
326
+ }
327
+ function updateQuadCorners(corners) {
328
+ projectorUniforms.quadHomography.value.copy(computeQuadHomography(corners));
329
+ }
284
330
  return {
285
331
  addTargetMesh,
286
332
  removeTargetMesh,
@@ -290,6 +336,8 @@ async function createVideoProjector(opts) {
290
336
  updateElevationDeg,
291
337
  updateRollDeg,
292
338
  updateOpacity,
339
+ updateCropRect,
340
+ updateQuadCorners,
293
341
  uniforms: projectorUniforms,
294
342
  overlays,
295
343
  targetMeshes,
package/dist/index.mjs CHANGED
@@ -1,5 +1,31 @@
1
1
  // src/three-video-projection.ts
2
2
  import * as THREE from "three";
3
+ function computeQuadHomography(corners) {
4
+ const [[x0, y0], [x1, y1], [x2, y2], [x3, y3]] = corners;
5
+ const dx1 = x1 - x2, dy1 = y1 - y2;
6
+ const dx2 = x3 - x2, dy2 = y3 - y2;
7
+ const dx3 = x0 - x1 + x2 - x3, dy3 = y0 - y1 + y2 - y3;
8
+ const den = dx1 * dy2 - dx2 * dy1;
9
+ const pg = (dx3 * dy2 - dx2 * dy3) / den;
10
+ const ph = (dx1 * dy3 - dx3 * dy1) / den;
11
+ const m00 = x1 - x0 + pg * x1, m01 = x3 - x0 + ph * x3, m02 = x0;
12
+ const m10 = y1 - y0 + pg * y1, m11 = y3 - y0 + ph * y3, m12 = y0;
13
+ const m20 = pg, m21 = ph, m22 = 1;
14
+ const det = m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20);
15
+ const inv = new THREE.Matrix3();
16
+ inv.set(
17
+ (m11 * m22 - m12 * m21) / det,
18
+ -(m01 * m22 - m02 * m21) / det,
19
+ (m01 * m12 - m02 * m11) / det,
20
+ -(m10 * m22 - m12 * m20) / det,
21
+ (m00 * m22 - m02 * m20) / det,
22
+ -(m00 * m12 - m02 * m10) / det,
23
+ (m10 * m21 - m11 * m20) / det,
24
+ -(m00 * m21 - m01 * m20) / det,
25
+ (m00 * m11 - m01 * m10) / det
26
+ );
27
+ return inv;
28
+ }
3
29
  async function createVideoProjector(opts) {
4
30
  const {
5
31
  scene,
@@ -13,6 +39,8 @@ async function createVideoProjector(opts) {
13
39
  opacity = 1,
14
40
  projBias = 1e-4,
15
41
  edgeFeather = 0.05,
42
+ cropRect = [0, 0, 1, 1],
43
+ quadCorners = [[0, 0], [1, 0], [1, 1], [0, 1]],
16
44
  isShowHelper = true
17
45
  } = opts;
18
46
  let orientParams = {
@@ -49,7 +77,9 @@ async function createVideoProjector(opts) {
49
77
  projectorDepthMap: { value: null },
50
78
  projBias: { value: projBias },
51
79
  edgeFeather: { value: edgeFeather },
52
- opacity: { value: opacity }
80
+ opacity: { value: opacity },
81
+ cropRect: { value: new THREE.Vector4(cropRect[0], cropRect[1], cropRect[2], cropRect[3]) },
82
+ quadHomography: { value: computeQuadHomography(quadCorners) }
53
83
  };
54
84
  const vertexShader = `
55
85
  varying vec3 vWorldPos;
@@ -69,6 +99,8 @@ async function createVideoProjector(opts) {
69
99
  uniform float projBias;
70
100
  uniform float edgeFeather;
71
101
  uniform float opacity;
102
+ uniform vec4 cropRect;
103
+ uniform mat3 quadHomography;
72
104
  varying vec3 vWorldPos;
73
105
  varying vec3 vWorldNormal;
74
106
 
@@ -77,8 +109,7 @@ async function createVideoProjector(opts) {
77
109
  if (projPos.w <= 0.0) discard;
78
110
  vec2 uv = projPos.xy / projPos.w * 0.5 + 0.5;
79
111
  if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) discard;
80
- vec4 color = texture(projectorMap, uv);
81
-
112
+
82
113
  // \u906E\u6321\u5254\u9664
83
114
  float projNDCz = projPos.z / projPos.w;
84
115
  float projDepth01 = projNDCz * 0.5 + 0.5;
@@ -87,13 +118,22 @@ async function createVideoProjector(opts) {
87
118
  discard;
88
119
  }
89
120
 
90
- // \u8FB9\u7F18\u7FBD\u5316
91
- vec2 adjUV = uv;
92
- float minDist = min(min(adjUV.x, 1.0 - adjUV.x), min(adjUV.y, 1.0 - adjUV.y));
121
+ // \u56DB\u89D2\u70B9\u53D8\u6362\uFF08\u6295\u5F71UV \u2192 \u89C6\u9891\u7EB9\u7406UV\uFF09
122
+ vec3 hw = quadHomography * vec3(uv, 1.0);
123
+ vec2 warpedUV = hw.xy / hw.z;
124
+
125
+ // \u81EA\u5B9A\u4E49\u88C1\u526A\u53CA\u7FBD\u5316
126
+ vec2 cropMin = cropRect.xy;
127
+ vec2 cropMax = cropRect.zw;
128
+ if (warpedUV.x < cropMin.x || warpedUV.x > cropMax.x || warpedUV.y < cropMin.y || warpedUV.y > cropMax.y) discard;
129
+ float distX = min(warpedUV.x - cropMin.x, cropMax.x - warpedUV.x);
130
+ float distY = min(warpedUV.y - cropMin.y, cropMax.y - warpedUV.y);
131
+ float minDist = min(distX, distY);
93
132
  float edgeFactor = 1.0;
94
133
  if (edgeFeather > 0.0) {
95
134
  edgeFactor = smoothstep(0.0, edgeFeather, minDist);
96
135
  }
136
+ vec4 color = texture(projectorMap, warpedUV);
97
137
  float effectiveAlpha = color.a * edgeFactor;
98
138
 
99
139
  // \u8F93\u51FA
@@ -246,6 +286,12 @@ async function createVideoProjector(opts) {
246
286
  const clamped = Math.max(0, Math.min(1, v));
247
287
  projectorUniforms.opacity.value = clamped;
248
288
  }
289
+ function updateCropRect(rect) {
290
+ projectorUniforms.cropRect.value.set(rect[0], rect[1], rect[2], rect[3]);
291
+ }
292
+ function updateQuadCorners(corners) {
293
+ projectorUniforms.quadHomography.value.copy(computeQuadHomography(corners));
294
+ }
249
295
  return {
250
296
  addTargetMesh,
251
297
  removeTargetMesh,
@@ -255,6 +301,8 @@ async function createVideoProjector(opts) {
255
301
  updateElevationDeg,
256
302
  updateRollDeg,
257
303
  updateOpacity,
304
+ updateCropRect,
305
+ updateQuadCorners,
258
306
  uniforms: projectorUniforms,
259
307
  overlays,
260
308
  targetMeshes,
package/package.json CHANGED
@@ -1,54 +1,52 @@
1
- {
2
- "name": "three-video-projection",
3
- "version": "0.0.9",
4
- "description": "Projector utility for projecting video textures onto meshes (three.js)",
5
- "main": "./dist/index.cjs",
6
- "module": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js",
12
- "require": "./dist/index.cjs"
13
- }
14
- },
15
- "files": [
16
- "dist"
17
- ],
18
- "keywords": [
19
- "three",
20
- "videoProjection",
21
- "videoFusion"
22
- ],
23
- "author": "hh-hang",
24
- "license": "MIT",
25
- "scripts": {
26
- "dev": "vite",
27
- "build": "tsup",
28
- "prepare": "npm run build",
29
- "build:cinema": "npm --prefix ./examples/cinema install && npm --prefix ./examples/cinema run build",
30
- "build:monitor": "npm --prefix ./examples/monitor install && npm --prefix ./examples/monitor run build",
31
- "collect": "node ./scripts/collect.js",
32
- "build:examples": "npm run build:cinema && npm run build:monitor && npm run collect"
33
- },
34
- "peerDependencies": {
35
- "three": "^0.182.0"
36
- },
37
- "devDependencies": {
38
- "@types/node": "^25.0.10",
39
- "@types/three": "^0.182.0",
40
- "@vitejs/plugin-vue": "^6.0.3",
41
- "tsup": "^8.5.1",
42
- "typescript": "^5.0.0",
43
- "vite": "^7.3.1",
44
- "vue": "^3.5.27",
45
- "hls.js": "^1.6.15",
46
- "stats.js": "^0.17.0",
47
- "three-player-controller": "^0.3.1"
48
- },
49
- "repository": {
50
- "type": "git",
51
- "url": "https://github.com/hh-hang/three-video-projection"
52
- },
53
- "homepage": "https://hh-hang.github.io/three-video-projection/"
54
- }
1
+ {
2
+ "name": "three-video-projection",
3
+ "version": "0.1.0",
4
+ "description": "Projector utility for projecting video textures onto meshes (three.js)",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "three",
20
+ "videoProjection",
21
+ "videoFusion"
22
+ ],
23
+ "author": "hh-hang",
24
+ "license": "MIT",
25
+ "scripts": {
26
+ "dev": "vite",
27
+ "build": "tsup",
28
+ "prepare": "npm run build",
29
+ "build:example": "vite build"
30
+ },
31
+ "peerDependencies": {
32
+ "three": "^0.182.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^25.0.10",
36
+ "@types/three": "^0.182.0",
37
+ "@vitejs/plugin-vue": "^6.0.3",
38
+ "hls.js": "^1.6.15",
39
+ "sass-embedded": "^1.98.0",
40
+ "stats.js": "^0.17.0",
41
+ "three-player-controller": "^0.3.1",
42
+ "tsup": "^8.5.1",
43
+ "typescript": "^5.0.0",
44
+ "vite": "^7.3.1",
45
+ "vue": "^3.5.27"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/hh-hang/three-video-projection"
50
+ },
51
+ "homepage": "https://hh-hang.github.io/three-video-projection/"
52
+ }