reze-engine 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 +3 -0
- package/dist/ammo-loader.d.ts +4 -0
- package/dist/ammo-loader.d.ts.map +1 -0
- package/dist/ammo-loader.js +29 -0
- package/dist/camera.d.ts +46 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/camera.js +303 -0
- package/dist/engine.d.ts +88 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +990 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/math.d.ts +52 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +389 -0
- package/dist/model.d.ts +102 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +416 -0
- package/dist/physics.d.ts +82 -0
- package/dist/physics.d.ts.map +1 -0
- package/dist/physics.js +503 -0
- package/dist/pmx-loader.d.ts +47 -0
- package/dist/pmx-loader.d.ts.map +1 -0
- package/dist/pmx-loader.js +938 -0
- package/package.json +42 -0
- package/src/ammo-loader.ts +35 -0
- package/src/camera.ts +358 -0
- package/src/engine.ts +1166 -0
- package/src/index.ts +1 -0
- package/src/math.ts +505 -0
- package/src/model.ts +586 -0
- package/src/physics.ts +680 -0
- package/src/pmx-loader.ts +1031 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ammo-loader.d.ts","sourceRoot":"","sources":["../src/ammo-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAKhD,wBAAsB,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC,CAyBtD;AAED,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAErD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
let ammoInstance = null;
|
|
2
|
+
let ammoPromise = null;
|
|
3
|
+
export async function loadAmmo() {
|
|
4
|
+
// Return cached instance if available
|
|
5
|
+
if (ammoInstance) {
|
|
6
|
+
return ammoInstance;
|
|
7
|
+
}
|
|
8
|
+
// Return existing promise if already loading
|
|
9
|
+
if (ammoPromise) {
|
|
10
|
+
return ammoPromise;
|
|
11
|
+
}
|
|
12
|
+
// Start loading Ammo
|
|
13
|
+
ammoPromise = (async () => {
|
|
14
|
+
try {
|
|
15
|
+
const { Ammo } = await import("@fred3d/ammo");
|
|
16
|
+
ammoInstance = await Ammo();
|
|
17
|
+
return ammoInstance;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.error("[Ammo] Failed to load:", error);
|
|
21
|
+
ammoPromise = null; // Reset promise so it can be retried
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
25
|
+
return ammoPromise;
|
|
26
|
+
}
|
|
27
|
+
export function getAmmoInstance() {
|
|
28
|
+
return ammoInstance;
|
|
29
|
+
}
|
package/dist/camera.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Mat4, Vec3 } from "./math";
|
|
2
|
+
export declare class Camera {
|
|
3
|
+
alpha: number;
|
|
4
|
+
beta: number;
|
|
5
|
+
radius: number;
|
|
6
|
+
target: Vec3;
|
|
7
|
+
fov: number;
|
|
8
|
+
aspect: number;
|
|
9
|
+
near: number;
|
|
10
|
+
far: number;
|
|
11
|
+
private canvas;
|
|
12
|
+
private isDragging;
|
|
13
|
+
private mouseButton;
|
|
14
|
+
private lastMousePos;
|
|
15
|
+
private lastTouchPos;
|
|
16
|
+
private touchIdentifier;
|
|
17
|
+
private isPinching;
|
|
18
|
+
private lastPinchDistance;
|
|
19
|
+
private lastPinchMidpoint;
|
|
20
|
+
private initialPinchDistance;
|
|
21
|
+
angularSensitivity: number;
|
|
22
|
+
panSensitivity: number;
|
|
23
|
+
wheelPrecision: number;
|
|
24
|
+
pinchPrecision: number;
|
|
25
|
+
minZ: number;
|
|
26
|
+
maxZ: number;
|
|
27
|
+
lowerBetaLimit: number;
|
|
28
|
+
upperBetaLimit: number;
|
|
29
|
+
constructor(alpha: number, beta: number, radius: number, target: Vec3, fov?: number);
|
|
30
|
+
getPosition(): Vec3;
|
|
31
|
+
getViewMatrix(): Mat4;
|
|
32
|
+
private getCameraVectors;
|
|
33
|
+
private panCamera;
|
|
34
|
+
getProjectionMatrix(): Mat4;
|
|
35
|
+
attachControl(canvas: HTMLCanvasElement): void;
|
|
36
|
+
detachControl(): void;
|
|
37
|
+
private onMouseDown;
|
|
38
|
+
private onMouseMove;
|
|
39
|
+
private onMouseUp;
|
|
40
|
+
private onWheel;
|
|
41
|
+
private onContextMenu;
|
|
42
|
+
private onTouchStart;
|
|
43
|
+
private onTouchMove;
|
|
44
|
+
private onTouchEnd;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=camera.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAInC,qBAAa,MAAM;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAI;IAClB,IAAI,EAAE,MAAM,CAAO;IACnB,GAAG,EAAE,MAAM,CAAM;IAGjB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAY;IACrC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,oBAAoB,CAAY;IAGxC,kBAAkB,EAAE,MAAM,CAAQ;IAClC,cAAc,EAAE,MAAM,CAAS;IAC/B,cAAc,EAAE,MAAM,CAAO;IAC7B,cAAc,EAAE,MAAM,CAAO;IAC7B,IAAI,EAAE,MAAM,CAAM;IAClB,IAAI,EAAE,MAAM,CAAM;IAClB,cAAc,EAAE,MAAM,CAAQ;IAC9B,cAAc,EAAE,MAAM,CAAkB;gBAE5B,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAE,MAAoB;IAkBhG,WAAW,IAAI,IAAI;IAQnB,aAAa,IAAI,IAAI;IAQrB,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,SAAS;IAiBjB,mBAAmB,IAAI,IAAI;IAI3B,aAAa,CAAC,MAAM,EAAE,iBAAiB;IAiBvC,aAAa;IAkBb,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,YAAY;IA6BpB,OAAO,CAAC,WAAW;IAiFnB,OAAO,CAAC,UAAU;CA+BnB"}
|
package/dist/camera.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { Mat4, Vec3 } from "./math";
|
|
2
|
+
const FAR = 1000;
|
|
3
|
+
export class Camera {
|
|
4
|
+
constructor(alpha, beta, radius, target, fov = Math.PI / 4) {
|
|
5
|
+
this.aspect = 1;
|
|
6
|
+
this.near = 0.05;
|
|
7
|
+
this.far = FAR;
|
|
8
|
+
// Input state
|
|
9
|
+
this.canvas = null;
|
|
10
|
+
this.isDragging = false;
|
|
11
|
+
this.mouseButton = null; // Track which mouse button is pressed (0 = left, 2 = right)
|
|
12
|
+
this.lastMousePos = { x: 0, y: 0 };
|
|
13
|
+
this.lastTouchPos = { x: 0, y: 0 };
|
|
14
|
+
this.touchIdentifier = null;
|
|
15
|
+
this.isPinching = false;
|
|
16
|
+
this.lastPinchDistance = 0;
|
|
17
|
+
this.lastPinchMidpoint = { x: 0, y: 0 }; // Midpoint of two fingers for panning
|
|
18
|
+
this.initialPinchDistance = 0; // Initial distance when pinch started
|
|
19
|
+
// Camera settings
|
|
20
|
+
this.angularSensitivity = 0.005;
|
|
21
|
+
this.panSensitivity = 0.0002; // Sensitivity for right-click panning
|
|
22
|
+
this.wheelPrecision = 0.01;
|
|
23
|
+
this.pinchPrecision = 0.05;
|
|
24
|
+
this.minZ = 0.1;
|
|
25
|
+
this.maxZ = FAR;
|
|
26
|
+
this.lowerBetaLimit = 0.001;
|
|
27
|
+
this.upperBetaLimit = Math.PI - 0.001;
|
|
28
|
+
this.alpha = alpha;
|
|
29
|
+
this.beta = beta;
|
|
30
|
+
this.radius = radius;
|
|
31
|
+
this.target = target;
|
|
32
|
+
this.fov = fov;
|
|
33
|
+
// Bind event handlers
|
|
34
|
+
this.onMouseDown = this.onMouseDown.bind(this);
|
|
35
|
+
this.onMouseMove = this.onMouseMove.bind(this);
|
|
36
|
+
this.onMouseUp = this.onMouseUp.bind(this);
|
|
37
|
+
this.onWheel = this.onWheel.bind(this);
|
|
38
|
+
this.onContextMenu = this.onContextMenu.bind(this);
|
|
39
|
+
this.onTouchStart = this.onTouchStart.bind(this);
|
|
40
|
+
this.onTouchMove = this.onTouchMove.bind(this);
|
|
41
|
+
this.onTouchEnd = this.onTouchEnd.bind(this);
|
|
42
|
+
}
|
|
43
|
+
getPosition() {
|
|
44
|
+
// Convert spherical coordinates to Cartesian position
|
|
45
|
+
const x = this.target.x + this.radius * Math.sin(this.beta) * Math.sin(this.alpha);
|
|
46
|
+
const y = this.target.y + this.radius * Math.cos(this.beta);
|
|
47
|
+
const z = this.target.z + this.radius * Math.sin(this.beta) * Math.cos(this.alpha);
|
|
48
|
+
return new Vec3(x, y, z);
|
|
49
|
+
}
|
|
50
|
+
getViewMatrix() {
|
|
51
|
+
const eye = this.getPosition();
|
|
52
|
+
const up = new Vec3(0, 1, 0);
|
|
53
|
+
return Mat4.lookAt(eye, this.target, up);
|
|
54
|
+
}
|
|
55
|
+
// Get camera's right and up vectors for panning
|
|
56
|
+
// Uses a more robust calculation similar to BabylonJS
|
|
57
|
+
getCameraVectors() {
|
|
58
|
+
const eye = this.getPosition();
|
|
59
|
+
const forward = this.target.subtract(eye);
|
|
60
|
+
const forwardLen = forward.length();
|
|
61
|
+
// Handle edge case where camera is at target
|
|
62
|
+
if (forwardLen < 0.0001) {
|
|
63
|
+
return { right: new Vec3(1, 0, 0), up: new Vec3(0, 1, 0) };
|
|
64
|
+
}
|
|
65
|
+
const forwardNorm = forward.scale(1 / forwardLen);
|
|
66
|
+
const worldUp = new Vec3(0, 1, 0);
|
|
67
|
+
// Calculate right vector: right = worldUp × forward
|
|
68
|
+
// Use a more stable calculation that handles parallel vectors
|
|
69
|
+
let right = worldUp.cross(forwardNorm);
|
|
70
|
+
const rightLen = right.length();
|
|
71
|
+
// If forward is parallel to worldUp, use a fallback
|
|
72
|
+
if (rightLen < 0.0001) {
|
|
73
|
+
// Camera is looking straight up or down, use X-axis as right
|
|
74
|
+
right = new Vec3(1, 0, 0);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
right = right.scale(1 / rightLen);
|
|
78
|
+
}
|
|
79
|
+
// Calculate camera up vector: up = forward × right (ensures orthogonality)
|
|
80
|
+
let up = forwardNorm.cross(right);
|
|
81
|
+
const upLen = up.length();
|
|
82
|
+
if (upLen < 0.0001) {
|
|
83
|
+
// Fallback to world up
|
|
84
|
+
up = new Vec3(0, 1, 0);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
up = up.scale(1 / upLen);
|
|
88
|
+
}
|
|
89
|
+
return { right, up };
|
|
90
|
+
}
|
|
91
|
+
// Pan the camera target based on mouse movement
|
|
92
|
+
// Uses screen-space to world-space translation similar to BabylonJS
|
|
93
|
+
panCamera(deltaX, deltaY) {
|
|
94
|
+
const { right, up } = this.getCameraVectors();
|
|
95
|
+
// Calculate pan distance based on camera distance
|
|
96
|
+
// The pan amount is proportional to the camera distance (radius) for consistent feel
|
|
97
|
+
// This makes panning feel natural at all zoom levels
|
|
98
|
+
const panDistance = this.radius * this.panSensitivity;
|
|
99
|
+
// Horizontal movement: drag right pans left (opposite direction)
|
|
100
|
+
// Vertical movement: drag up pans up (positive up vector)
|
|
101
|
+
const panRight = right.scale(-deltaX * panDistance);
|
|
102
|
+
const panUp = up.scale(deltaY * panDistance);
|
|
103
|
+
// Update target position smoothly
|
|
104
|
+
this.target = this.target.add(panRight).add(panUp);
|
|
105
|
+
}
|
|
106
|
+
getProjectionMatrix() {
|
|
107
|
+
return Mat4.perspective(this.fov, this.aspect, this.near, this.far);
|
|
108
|
+
}
|
|
109
|
+
attachControl(canvas) {
|
|
110
|
+
this.canvas = canvas;
|
|
111
|
+
// Attach mouse event listeners
|
|
112
|
+
// mousedown on canvas, but move/up on window so dragging works everywhere
|
|
113
|
+
this.canvas.addEventListener("mousedown", this.onMouseDown);
|
|
114
|
+
window.addEventListener("mousemove", this.onMouseMove);
|
|
115
|
+
window.addEventListener("mouseup", this.onMouseUp);
|
|
116
|
+
this.canvas.addEventListener("wheel", this.onWheel, { passive: false });
|
|
117
|
+
this.canvas.addEventListener("contextmenu", this.onContextMenu);
|
|
118
|
+
// Attach touch event listeners for mobile
|
|
119
|
+
this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: false });
|
|
120
|
+
window.addEventListener("touchmove", this.onTouchMove, { passive: false });
|
|
121
|
+
window.addEventListener("touchend", this.onTouchEnd);
|
|
122
|
+
}
|
|
123
|
+
detachControl() {
|
|
124
|
+
if (!this.canvas)
|
|
125
|
+
return;
|
|
126
|
+
// Remove mouse event listeners
|
|
127
|
+
this.canvas.removeEventListener("mousedown", this.onMouseDown);
|
|
128
|
+
window.removeEventListener("mousemove", this.onMouseMove);
|
|
129
|
+
window.removeEventListener("mouseup", this.onMouseUp);
|
|
130
|
+
this.canvas.removeEventListener("wheel", this.onWheel);
|
|
131
|
+
this.canvas.removeEventListener("contextmenu", this.onContextMenu);
|
|
132
|
+
// Remove touch event listeners
|
|
133
|
+
this.canvas.removeEventListener("touchstart", this.onTouchStart);
|
|
134
|
+
window.removeEventListener("touchmove", this.onTouchMove);
|
|
135
|
+
window.removeEventListener("touchend", this.onTouchEnd);
|
|
136
|
+
this.canvas = null;
|
|
137
|
+
}
|
|
138
|
+
onMouseDown(e) {
|
|
139
|
+
this.isDragging = true;
|
|
140
|
+
this.mouseButton = e.button;
|
|
141
|
+
this.lastMousePos = { x: e.clientX, y: e.clientY };
|
|
142
|
+
}
|
|
143
|
+
onMouseMove(e) {
|
|
144
|
+
if (!this.isDragging)
|
|
145
|
+
return;
|
|
146
|
+
const deltaX = e.clientX - this.lastMousePos.x;
|
|
147
|
+
const deltaY = e.clientY - this.lastMousePos.y;
|
|
148
|
+
if (this.mouseButton === 2) {
|
|
149
|
+
// Right-click: pan the camera target
|
|
150
|
+
this.panCamera(deltaX, deltaY);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// Left-click (or default): rotate the camera
|
|
154
|
+
this.alpha += deltaX * this.angularSensitivity;
|
|
155
|
+
this.beta -= deltaY * this.angularSensitivity;
|
|
156
|
+
// Clamp beta to prevent flipping
|
|
157
|
+
this.beta = Math.max(this.lowerBetaLimit, Math.min(this.upperBetaLimit, this.beta));
|
|
158
|
+
}
|
|
159
|
+
this.lastMousePos = { x: e.clientX, y: e.clientY };
|
|
160
|
+
}
|
|
161
|
+
onMouseUp() {
|
|
162
|
+
this.isDragging = false;
|
|
163
|
+
this.mouseButton = null;
|
|
164
|
+
}
|
|
165
|
+
onWheel(e) {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
// Update camera radius (zoom)
|
|
168
|
+
this.radius += e.deltaY * this.wheelPrecision;
|
|
169
|
+
// Clamp radius to reasonable bounds
|
|
170
|
+
this.radius = Math.max(this.minZ, Math.min(this.maxZ, this.radius));
|
|
171
|
+
// Expand far plane to keep scene visible when zooming out
|
|
172
|
+
this.far = Math.max(FAR, this.radius * 4);
|
|
173
|
+
}
|
|
174
|
+
onContextMenu(e) {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
}
|
|
177
|
+
onTouchStart(e) {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
if (e.touches.length === 1) {
|
|
180
|
+
// Single touch - rotation
|
|
181
|
+
const touch = e.touches[0];
|
|
182
|
+
this.isDragging = true;
|
|
183
|
+
this.isPinching = false;
|
|
184
|
+
this.touchIdentifier = touch.identifier;
|
|
185
|
+
this.lastTouchPos = { x: touch.clientX, y: touch.clientY };
|
|
186
|
+
}
|
|
187
|
+
else if (e.touches.length === 2) {
|
|
188
|
+
// Two touches - can be pinch zoom or pan
|
|
189
|
+
this.isDragging = false;
|
|
190
|
+
this.isPinching = true;
|
|
191
|
+
const touch1 = e.touches[0];
|
|
192
|
+
const touch2 = e.touches[1];
|
|
193
|
+
const dx = touch2.clientX - touch1.clientX;
|
|
194
|
+
const dy = touch2.clientY - touch1.clientY;
|
|
195
|
+
this.lastPinchDistance = Math.sqrt(dx * dx + dy * dy);
|
|
196
|
+
this.initialPinchDistance = this.lastPinchDistance;
|
|
197
|
+
// Calculate initial midpoint for panning
|
|
198
|
+
this.lastPinchMidpoint = {
|
|
199
|
+
x: (touch1.clientX + touch2.clientX) / 2,
|
|
200
|
+
y: (touch1.clientY + touch2.clientY) / 2,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
onTouchMove(e) {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
if (this.isPinching && e.touches.length === 2) {
|
|
207
|
+
// Two-finger gesture: can be pinch zoom or pan
|
|
208
|
+
const touch1 = e.touches[0];
|
|
209
|
+
const touch2 = e.touches[1];
|
|
210
|
+
const dx = touch2.clientX - touch1.clientX;
|
|
211
|
+
const dy = touch2.clientY - touch1.clientY;
|
|
212
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
213
|
+
// Calculate current midpoint
|
|
214
|
+
const currentMidpoint = {
|
|
215
|
+
x: (touch1.clientX + touch2.clientX) / 2,
|
|
216
|
+
y: (touch1.clientY + touch2.clientY) / 2,
|
|
217
|
+
};
|
|
218
|
+
// Calculate distance change and midpoint movement
|
|
219
|
+
const distanceDelta = Math.abs(distance - this.lastPinchDistance);
|
|
220
|
+
const midpointDeltaX = currentMidpoint.x - this.lastPinchMidpoint.x;
|
|
221
|
+
const midpointDeltaY = currentMidpoint.y - this.lastPinchMidpoint.y;
|
|
222
|
+
const midpointDelta = Math.sqrt(midpointDeltaX * midpointDeltaX + midpointDeltaY * midpointDeltaY);
|
|
223
|
+
// Determine gesture type based on relative changes
|
|
224
|
+
// Calculate relative change in distance (as percentage of initial distance)
|
|
225
|
+
const distanceChangeRatio = distanceDelta / Math.max(this.initialPinchDistance, 10.0);
|
|
226
|
+
// Threshold: if distance changes more than 3% of initial, it's primarily a zoom gesture
|
|
227
|
+
// Otherwise, if midpoint moves significantly, it's a pan gesture
|
|
228
|
+
const ZOOM_THRESHOLD = 0.03;
|
|
229
|
+
const PAN_THRESHOLD = 2.0; // Minimum pixels of midpoint movement for pan
|
|
230
|
+
const isZoomGesture = distanceChangeRatio > ZOOM_THRESHOLD;
|
|
231
|
+
const isPanGesture = midpointDelta > PAN_THRESHOLD && distanceChangeRatio < ZOOM_THRESHOLD * 2;
|
|
232
|
+
if (isZoomGesture) {
|
|
233
|
+
// Primary gesture is zoom (pinch)
|
|
234
|
+
const delta = this.lastPinchDistance - distance;
|
|
235
|
+
this.radius += delta * this.pinchPrecision;
|
|
236
|
+
// Clamp radius to reasonable bounds
|
|
237
|
+
this.radius = Math.max(this.minZ, Math.min(this.maxZ, this.radius));
|
|
238
|
+
// Expand far plane for pinch zoom as well
|
|
239
|
+
this.far = Math.max(FAR, this.radius * 4);
|
|
240
|
+
}
|
|
241
|
+
if (isPanGesture) {
|
|
242
|
+
// Primary gesture is pan (two-finger drag)
|
|
243
|
+
// Use panning similar to right-click pan
|
|
244
|
+
this.panCamera(midpointDeltaX, midpointDeltaY);
|
|
245
|
+
}
|
|
246
|
+
// Update tracking values
|
|
247
|
+
this.lastPinchDistance = distance;
|
|
248
|
+
this.lastPinchMidpoint = currentMidpoint;
|
|
249
|
+
}
|
|
250
|
+
else if (this.isDragging && this.touchIdentifier !== null) {
|
|
251
|
+
// Single-finger rotation
|
|
252
|
+
// Find the touch we're tracking
|
|
253
|
+
let touch = null;
|
|
254
|
+
for (let i = 0; i < e.touches.length; i++) {
|
|
255
|
+
if (e.touches[i].identifier === this.touchIdentifier) {
|
|
256
|
+
touch = e.touches[i];
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!touch)
|
|
261
|
+
return;
|
|
262
|
+
const deltaX = touch.clientX - this.lastTouchPos.x;
|
|
263
|
+
const deltaY = touch.clientY - this.lastTouchPos.y;
|
|
264
|
+
this.alpha += deltaX * this.angularSensitivity;
|
|
265
|
+
this.beta -= deltaY * this.angularSensitivity;
|
|
266
|
+
// Clamp beta to prevent flipping
|
|
267
|
+
this.beta = Math.max(this.lowerBetaLimit, Math.min(this.upperBetaLimit, this.beta));
|
|
268
|
+
this.lastTouchPos = { x: touch.clientX, y: touch.clientY };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
onTouchEnd(e) {
|
|
272
|
+
if (e.touches.length === 0) {
|
|
273
|
+
// All touches ended
|
|
274
|
+
this.isDragging = false;
|
|
275
|
+
this.isPinching = false;
|
|
276
|
+
this.touchIdentifier = null;
|
|
277
|
+
this.initialPinchDistance = 0;
|
|
278
|
+
}
|
|
279
|
+
else if (e.touches.length === 1 && this.isPinching) {
|
|
280
|
+
// Went from 2 fingers to 1 - switch to rotation
|
|
281
|
+
const touch = e.touches[0];
|
|
282
|
+
this.isPinching = false;
|
|
283
|
+
this.isDragging = true;
|
|
284
|
+
this.touchIdentifier = touch.identifier;
|
|
285
|
+
this.lastTouchPos = { x: touch.clientX, y: touch.clientY };
|
|
286
|
+
this.initialPinchDistance = 0;
|
|
287
|
+
}
|
|
288
|
+
else if (this.touchIdentifier !== null) {
|
|
289
|
+
// Check if our tracked touch ended
|
|
290
|
+
let touchStillActive = false;
|
|
291
|
+
for (let i = 0; i < e.touches.length; i++) {
|
|
292
|
+
if (e.touches[i].identifier === this.touchIdentifier) {
|
|
293
|
+
touchStillActive = true;
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (!touchStillActive) {
|
|
298
|
+
this.isDragging = false;
|
|
299
|
+
this.touchIdentifier = null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Camera } from "./camera";
|
|
2
|
+
import { Vec3 } from "./math";
|
|
3
|
+
export interface EngineStats {
|
|
4
|
+
fps: number;
|
|
5
|
+
frameTime: number;
|
|
6
|
+
memoryUsed: number;
|
|
7
|
+
vertices: number;
|
|
8
|
+
drawCalls: number;
|
|
9
|
+
triangles: number;
|
|
10
|
+
materials: number;
|
|
11
|
+
textures: number;
|
|
12
|
+
textureMemory: number;
|
|
13
|
+
bufferMemory: number;
|
|
14
|
+
gpuMemory: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class Engine {
|
|
17
|
+
private canvas;
|
|
18
|
+
private device;
|
|
19
|
+
private context;
|
|
20
|
+
private presentationFormat;
|
|
21
|
+
camera: Camera;
|
|
22
|
+
private cameraUniformBuffer;
|
|
23
|
+
private cameraMatrixData;
|
|
24
|
+
private lightUniformBuffer;
|
|
25
|
+
private lightData;
|
|
26
|
+
private lightCount;
|
|
27
|
+
private vertexBuffer;
|
|
28
|
+
private vertexCount;
|
|
29
|
+
private indexBuffer?;
|
|
30
|
+
private resizeObserver;
|
|
31
|
+
private depthTexture;
|
|
32
|
+
private pipeline;
|
|
33
|
+
private outlinePipeline;
|
|
34
|
+
private jointsBuffer;
|
|
35
|
+
private weightsBuffer;
|
|
36
|
+
private skinMatrixBuffer?;
|
|
37
|
+
private worldMatrixBuffer?;
|
|
38
|
+
private inverseBindMatrixBuffer?;
|
|
39
|
+
private skinMatrixComputePipeline?;
|
|
40
|
+
private boneCountBuffer?;
|
|
41
|
+
private multisampleTexture;
|
|
42
|
+
private readonly sampleCount;
|
|
43
|
+
private renderPassDescriptor;
|
|
44
|
+
private currentModel;
|
|
45
|
+
private modelDir;
|
|
46
|
+
private physics;
|
|
47
|
+
private textureSampler;
|
|
48
|
+
private textureCache;
|
|
49
|
+
private textureSizes;
|
|
50
|
+
private lastFpsUpdate;
|
|
51
|
+
private framesSinceLastUpdate;
|
|
52
|
+
private frameTimeSamples;
|
|
53
|
+
private frameTimeSum;
|
|
54
|
+
private drawCallCount;
|
|
55
|
+
private lastFrameTime;
|
|
56
|
+
private stats;
|
|
57
|
+
private animationFrameId;
|
|
58
|
+
private renderLoopCallback;
|
|
59
|
+
constructor(canvas: HTMLCanvasElement);
|
|
60
|
+
init(): Promise<void>;
|
|
61
|
+
private createPipelines;
|
|
62
|
+
private createSkinMatrixComputePipeline;
|
|
63
|
+
private setupResize;
|
|
64
|
+
private handleResize;
|
|
65
|
+
private setupCamera;
|
|
66
|
+
private setupLighting;
|
|
67
|
+
addLight(direction: Vec3, color: Vec3, intensity?: number): boolean;
|
|
68
|
+
setAmbient(intensity: number): void;
|
|
69
|
+
getStats(): EngineStats;
|
|
70
|
+
runRenderLoop(callback?: () => void): void;
|
|
71
|
+
stopRenderLoop(): void;
|
|
72
|
+
dispose(): void;
|
|
73
|
+
loadModel(path: string): Promise<void>;
|
|
74
|
+
private setupModelBuffers;
|
|
75
|
+
private materialDraws;
|
|
76
|
+
private outlineDraws;
|
|
77
|
+
private setupMaterials;
|
|
78
|
+
private createTextureFromPath;
|
|
79
|
+
render(): void;
|
|
80
|
+
private updateCameraUniforms;
|
|
81
|
+
private updateRenderTarget;
|
|
82
|
+
private updateModelPose;
|
|
83
|
+
private computeSkinMatrices;
|
|
84
|
+
private drawOutlines;
|
|
85
|
+
private drawModel;
|
|
86
|
+
private updateStats;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|