@needle-tools/engine 4.11.0-next.c7baa24 → 4.11.0-next.cc37c71

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.
Files changed (26) hide show
  1. package/dist/{needle-engine.bundle-tE15q9uM.js → needle-engine.bundle-BPZ6emFK.js} +2635 -2570
  2. package/dist/{needle-engine.bundle-DBDKvYj5.umd.cjs → needle-engine.bundle-CTY0RgBZ.umd.cjs} +97 -97
  3. package/dist/{needle-engine.bundle-qNZSuWhD.min.js → needle-engine.bundle-JV2ghuCa.min.js} +100 -100
  4. package/dist/needle-engine.js +2 -2
  5. package/dist/needle-engine.min.js +1 -1
  6. package/dist/needle-engine.umd.cjs +1 -1
  7. package/lib/engine-components/OrbitControls.js +2 -0
  8. package/lib/engine-components/OrbitControls.js.map +1 -1
  9. package/lib/engine-components/splines/SplineWalker.d.ts +43 -4
  10. package/lib/engine-components/splines/SplineWalker.js +88 -12
  11. package/lib/engine-components/splines/SplineWalker.js.map +1 -1
  12. package/lib/engine-components/web/Clickthrough.d.ts +2 -0
  13. package/lib/engine-components/web/Clickthrough.js +23 -1
  14. package/lib/engine-components/web/Clickthrough.js.map +1 -1
  15. package/lib/engine-components/web/ScrollFollow.d.ts +1 -9
  16. package/lib/engine-components/web/ScrollFollow.js +12 -28
  17. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  18. package/lib/engine-components/web/ViewBox.d.ts +16 -0
  19. package/lib/engine-components/web/ViewBox.js +35 -3
  20. package/lib/engine-components/web/ViewBox.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/engine-components/OrbitControls.ts +5 -2
  23. package/src/engine-components/splines/SplineWalker.ts +99 -14
  24. package/src/engine-components/web/Clickthrough.ts +28 -1
  25. package/src/engine-components/web/ScrollFollow.ts +15 -33
  26. package/src/engine-components/web/ViewBox.ts +35 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "4.11.0-next.c7baa24",
3
+ "version": "4.11.0-next.cc37c71",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.",
5
5
  "main": "dist/needle-engine.min.js",
6
6
  "exports": {
@@ -711,8 +711,10 @@ export class OrbitControls extends Behaviour implements ICameraController {
711
711
 
712
712
 
713
713
  if (this._controls) {
714
- if (this.debugLog)
715
- this._controls.domElement = this.context.renderer.domElement;
714
+ if (this.debugLog) this._controls.domElement = this.context.renderer.domElement;
715
+
716
+ const viewZoomFactor = 1 / (this.context.focusRectSettings?.zoom || 1);
717
+
716
718
  this._controls.enabled = !this._shouldDisable && this._camera === this.context.mainCameraComponent && !this.context.isInXR && !this._activePointerEvents.some(e => e.used);
717
719
  this._controls.keys = this.enableKeys ? defaultKeys : disabledKeys;
718
720
  this._controls.autoRotate = this.autoRotate;
@@ -723,6 +725,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
723
725
  this._controls.enableDamping = this.enableDamping;
724
726
  this._controls.dampingFactor = this.dampingFactor;
725
727
  this._controls.enablePan = this.enablePan;
728
+ this._controls.panSpeed = viewZoomFactor;
726
729
  this._controls.enableRotate = this.enableRotate;
727
730
  this._controls.minAzimuthAngle = this.minAzimuthAngle;
728
731
  this._controls.maxAzimuthAngle = this.maxAzimuthAngle;
@@ -1,5 +1,5 @@
1
1
 
2
- import { Object3D } from "three"
2
+ import { Object3D, Vector3 } from "three"
3
3
 
4
4
  import { Mathf } from "../../engine/engine_math.js";
5
5
  import { serializeable } from "../../engine/engine_serialization_decorator.js";
@@ -7,7 +7,10 @@ import { Behaviour } from "../Component.js";
7
7
  import { SplineContainer } from "./Spline.js";
8
8
 
9
9
  /**
10
- * Moves an object along a spline. Use this with a SplineContainer component.
10
+ * Moves an object along a spline.
11
+ * Use this with a SplineContainer component.
12
+ *
13
+ * - Example http://samples.needle.tools/splines
11
14
  */
12
15
  export class SplineWalker extends Behaviour {
13
16
 
@@ -17,18 +20,35 @@ export class SplineWalker extends Behaviour {
17
20
  @serializeable(SplineContainer)
18
21
  spline: SplineContainer | null = null;
19
22
 
20
- /** The object to move along the spline. If object is undefined then the spline walker will use the gameObject the component has been added to
23
+ /**
24
+ * The object to move along the spline.
25
+ * If object is undefined then the spline walker will use it's own object (gameObject).
26
+ * If object is null the spline walker will not move any object.
21
27
  * @default undefined
22
28
  */
23
29
  @serializeable(Object3D)
24
30
  object?: Object3D | null = undefined;
25
31
 
26
- /** The object to look at while moving along the spline
32
+
33
+
34
+ /**
35
+ * If true the object will rotate to look in the direction of the spline while moving along it.
36
+ * @default true
37
+ */
38
+ @serializeable()
39
+ useLookAt = true;
40
+
41
+ /**
42
+ * The object to look at while moving along the spline.
43
+ * If null the object will look in the direction of the spline.
44
+ * This can be disabled by setting useLookAt to false.
27
45
  * @default null
28
46
  */
29
47
  @serializeable(Object3D)
30
48
  lookAt: Object3D | null = null;
31
-
49
+
50
+
51
+
32
52
  /**
33
53
  * When clamp is set to true, the position01 value will be clamped between 0 and 1 and the object will not loop the spline.
34
54
  * @default false
@@ -36,7 +56,10 @@ export class SplineWalker extends Behaviour {
36
56
  @serializeable()
37
57
  clamp: boolean = false;
38
58
 
39
- /** The current position on the spline. The value ranges from 0 (start of the spline curve) to 1 (end of the spline curve)
59
+ /**
60
+ * The current position on the spline. The value ranges from 0 (start of the spline curve) to 1 (end of the spline curve)
61
+ *
62
+ * When setting this value, the position will be updated in the next frame.
40
63
  * @default 0
41
64
  */
42
65
  // @type float
@@ -46,7 +69,7 @@ export class SplineWalker extends Behaviour {
46
69
  }
47
70
  set position01(v: number) {
48
71
  this._position01 = v;
49
- this.updateFromPosition();
72
+ this._needsUpdate = true;
50
73
  }
51
74
 
52
75
  /** Resets the position to 0 */
@@ -68,25 +91,60 @@ export class SplineWalker extends Behaviour {
68
91
  @serializeable()
69
92
  duration: number = 10;
70
93
 
94
+ /**
95
+ * The strength with which the object is pulled to the spline.
96
+ * This can be used to create a "rubber band" effect when the object is moved away from the spline by other forces.
97
+ * A value of 0 means no pull, a value of 1 means the object is always on the spline.
98
+ * @default 1
99
+ */
100
+ pullStrength: number = 1;
101
+
71
102
 
72
103
  // #region internal
73
104
 
74
105
  private _position01: number = 0;
106
+ private _needsUpdate = false;
75
107
 
76
108
  /** @internal */
77
109
  start() {
78
- if(this.object === undefined) this.object = this.gameObject;
110
+ if (this.object === undefined) this.object = this.gameObject;
79
111
  this.updateFromPosition();
80
112
  }
81
113
 
114
+ /** @internal */
115
+ onEnable(): void {
116
+ window.addEventListener("pointerdown", this.onUserInput, { passive: true });
117
+ window.addEventListener("wheel", this.onUserInput, { passive: true });
118
+ }
119
+ /** @internal */
120
+ onDisable(): void {
121
+ window.removeEventListener("pointerdown", this.onUserInput);
122
+ window.removeEventListener("wheel", this.onUserInput);
123
+ }
124
+ private onUserInput = () => {
125
+ if (this.object?.contains(this.context.mainCamera)) {
126
+ this._needsUpdate = false;
127
+ this._performedUpdates += 999;
128
+ }
129
+ }
130
+
131
+ /** @internal */
82
132
  update() {
83
133
  if (this.autoRun) {
134
+ this._needsUpdate = true;
84
135
  this._position01 += this.context.time.deltaTime / this.duration;
136
+ }
137
+
138
+ if (this._needsUpdate) {
139
+ this._needsUpdate = false;
85
140
  this.updateFromPosition();
86
141
  }
87
142
  }
88
143
 
89
-
144
+ /**
145
+ * Updates the position of the object based on the current position01 value.
146
+ * @internal
147
+ */
90
148
  private updateFromPosition() {
91
149
  if (!this.spline || !this.spline.curve) return;
92
150
  if (!this.object) return;
@@ -96,11 +154,38 @@ export class SplineWalker extends Behaviour {
96
154
 
97
155
  const t = this._position01 >= 1 ? 1 : this._position01 % 1;
98
156
  const pt = this.spline.getPointAt(t);
99
- this.object.worldPosition = pt;
100
- if (!this.lookAt) {
101
- const tan = this.spline.getTangentAt(t);
102
- this.object.lookAt(pt.add(tan));
157
+
158
+ if (this.pullStrength >= 1) {
159
+ this.object.worldPosition = pt;
160
+ }
161
+ else {
162
+ if (this._position01 !== this._lastPosition01) {
163
+ this._performedUpdates = 0;
164
+ }
165
+ this._requiredUpdates = Math.round(100 / this.pullStrength);
166
+ if (this._performedUpdates < this._requiredUpdates) {
167
+ const wp = this.object.worldPosition;
168
+ this._performedUpdates++;
169
+ const pull = Mathf.clamp01(this.pullStrength);
170
+ const newPosition = this.object.worldPosition = wp.lerp(pt, pull * (this.context.time.deltaTime / .3));
171
+ this._lastPositionVector.copy(newPosition);
172
+ this._needsUpdate = true;
173
+ }
103
174
  }
104
- else this.object.lookAt(this.lookAt.worldPosition);
175
+
176
+ if (this.useLookAt) {
177
+ if (!this.lookAt) {
178
+ const tan = this.spline.getTangentAt(t);
179
+ this.object.lookAt(pt.add(tan));
180
+ }
181
+ else this.object.lookAt(this.lookAt.worldPosition);
182
+ }
183
+
184
+ this._lastPosition01 = this._position01;
105
185
  }
186
+
187
+ private _lastPosition01 = 0;
188
+ private _requiredUpdates: number = 0;
189
+ private _performedUpdates: number = 0;
190
+ private _lastPositionVector = new Vector3();
106
191
  }
@@ -1,3 +1,4 @@
1
+ import { Vector2 } from "three";
1
2
  import { NEPointerEvent } from "../../engine/engine_input.js";
2
3
  import { onStart } from "../../engine/engine_lifecycle_api.js";
3
4
  import { addAttributeChangeCallback } from "../../engine/engine_utils.js";
@@ -45,12 +46,14 @@ export class ClickThrough extends Behaviour {
45
46
  this.context.input.addEventListener('pointermove', this.onPointerEvent, {
46
47
  queue: 100,
47
48
  });
49
+ window.addEventListener("touchstart", this.onTouchStart, { passive: true });
48
50
  window.addEventListener("touchend", this.onTouchEnd, { passive: true });
49
51
  this._previousPointerEvents = this.context.domElement.style.pointerEvents;
50
52
  }
51
53
  onDisable() {
52
54
  this.context.input.removeEventListener('pointerdown', this.onPointerEvent);
53
55
  this.context.input.removeEventListener('pointermove', this.onPointerEvent);
56
+ window.removeEventListener("touchstart", this.onTouchStart);
54
57
  window.removeEventListener("touchend", this.onTouchEnd);
55
58
  this.context.domElement.style.pointerEvents = this._previousPointerEvents;
56
59
  }
@@ -69,9 +72,33 @@ export class ClickThrough extends Behaviour {
69
72
  }
70
73
  };
71
74
 
75
+
76
+
77
+ // #region Touch hack
78
+
79
+ private _touchDidHitAnything = false;
80
+
81
+ private onTouchStart = (_evt: TouchEvent) => {
82
+
83
+ const touch = _evt.touches[0];
84
+ if (!touch) return;
85
+
86
+ const ndx = touch.clientX / window.innerWidth * 2 - 1;
87
+ const ndy = -(touch.clientY / window.innerHeight) * 2 + 1;
88
+ // console.log(ndx, ndy);
89
+ const hits = this.context.physics.raycast({
90
+ screenPoint: new Vector2(ndx, ndy),
91
+ })
92
+ if (hits.length > 0) {
93
+ this._touchDidHitAnything = true;
94
+ }
95
+ }
96
+
72
97
  private onTouchEnd = (_evt: TouchEvent) => {
98
+ const _didHit = this._touchDidHitAnything;
99
+ this._touchDidHitAnything = false;
73
100
  setTimeout(() => {
74
- this.context.domElement.style.pointerEvents = 'all';
101
+ if (_didHit) this.context.domElement.style.pointerEvents = 'all';
75
102
  }, 100);
76
103
  }
77
104
  }
@@ -96,6 +96,7 @@ export class ScrollFollow extends Behaviour {
96
96
  @serializable()
97
97
  invert: boolean = false;
98
98
 
99
+
99
100
  /**
100
101
  * **Experimental - might change in future updates**
101
102
  * If set, the scroll position will be read from the specified element instead of the window.
@@ -106,7 +107,7 @@ export class ScrollFollow extends Behaviour {
106
107
  htmlSelector: string | null = null;
107
108
 
108
109
  @serializable()
109
- private mode: "window" = "window";
110
+ mode: "window" = "window";
110
111
 
111
112
  /**
112
113
  * Event fired when the scroll position changes
@@ -126,11 +127,6 @@ export class ScrollFollow extends Behaviour {
126
127
  private _appliedValue: number = -1;
127
128
 
128
129
 
129
- private _scrollStart: number = 0;
130
- private _scrollEnd: number = 0;
131
- private _scrollValue: number = 0;
132
- private _scrollContainerHeight: number = 0;
133
-
134
130
  /** @internal */
135
131
  onEnable() {
136
132
  window.addEventListener("wheel", this.updateCurrentScrollValue, { passive: true });
@@ -159,8 +155,7 @@ export class ScrollFollow extends Behaviour {
159
155
  }
160
156
  }
161
157
 
162
- // if (this._current_value !== this._appliedValue)
163
- {
158
+ if (this._current_value !== this._appliedValue) {
164
159
  this._appliedValue = this._current_value;
165
160
 
166
161
  let defaultPrevented = false;
@@ -182,9 +177,6 @@ export class ScrollFollow extends Behaviour {
182
177
 
183
178
  const value = this.invert ? 1 - this._current_value : this._current_value;
184
179
 
185
- // const height = this._rangeEndValue - this._rangeStartValue;
186
- // const pixelValue = this._rangeStartValue + value * height;
187
-
188
180
  // apply scroll to target(s)
189
181
  if (Array.isArray(this.target)) {
190
182
  this.target.forEach(t => t && this.applyScroll(t, value));
@@ -202,16 +194,11 @@ export class ScrollFollow extends Behaviour {
202
194
 
203
195
  private _lastSelectorValue: string | null = null;
204
196
  private _lastSelectorElement: Element | null = null;
205
- /** Top y */
206
- private _rangeStartValue: number = 0;
207
- /** Bottom y */
208
- private _rangeEndValue: number = 0;
209
197
 
210
198
  private updateCurrentScrollValue = () => {
211
199
 
212
200
  switch (this.mode) {
213
201
  case "window":
214
-
215
202
  if (this.htmlSelector?.length) {
216
203
  if (this.htmlSelector !== this._lastSelectorValue) {
217
204
  this._lastSelectorElement = document.querySelector(this.htmlSelector);
@@ -219,26 +206,20 @@ export class ScrollFollow extends Behaviour {
219
206
  }
220
207
  if (this._lastSelectorElement) {
221
208
  const rect = this._lastSelectorElement.getBoundingClientRect();
222
-
223
- this._scrollStart = rect.top + window.scrollY;
224
- this._scrollEnd = rect.height - window.innerHeight;
225
- this._scrollValue = -rect.top;
226
209
  this._target_value = -rect.top / (rect.height - window.innerHeight);
227
- this._rangeStartValue = rect.top + window.scrollY;
228
- this._rangeEndValue = this._rangeStartValue + rect.height - window.innerHeight;
229
- this._scrollContainerHeight = rect.height;
230
210
  break;
231
211
  }
232
212
  }
233
213
  else {
234
- this._scrollStart = 0;
235
- this._scrollEnd = window.document.body.scrollHeight - window.innerHeight;
236
- this._scrollValue = window.scrollY;
237
- this._target_value = this._scrollValue / (this._scrollEnd || 1);
238
- this._rangeStartValue = 0;
239
- this._rangeEndValue = document.body.scrollHeight;
240
- this._scrollContainerHeight = window.innerHeight;
214
+ if (window.document.body.scrollHeight <= window.innerHeight) {
215
+ // If the page is not scrollable we can still increment the scroll value to allow triggering timelines etc.
216
+ }
217
+ else {
218
+ const diff = window.document.body.scrollHeight - window.innerHeight;
219
+ this._target_value = window.scrollY / (diff || 1);
220
+ }
241
221
  }
222
+
242
223
  break;
243
224
  }
244
225
 
@@ -272,11 +253,12 @@ export class ScrollFollow extends Behaviour {
272
253
  target.intensity = value;
273
254
  }
274
255
  else if (target instanceof Object3D) {
256
+ const t = target as any;
275
257
  // When objects are assigned they're expected to move vertically based on scroll
276
- if (target["needle:scrollbounds"] === undefined) {
277
- target["needle:scrollbounds"] = getBoundingBox(target) || null;
258
+ if (t["needle:scrollbounds"] === undefined) {
259
+ t["needle:scrollbounds"] = getBoundingBox(target) || null;
278
260
  }
279
- const bounds = target["needle:scrollbounds"] as Box3;
261
+ const bounds = t["needle:scrollbounds"] as Box3;
280
262
  if (bounds) {
281
263
  // TODO: remap position to use upper screen edge and lower edge instead of center
282
264
  target.position.y = -bounds.min.y - value * (bounds.max.y - bounds.min.y);
@@ -1,4 +1,4 @@
1
- import { Camera, Matrix4, PerspectiveCamera, Quaternion, Scene, Vector2, Vector3 } from "three";
1
+ import { Camera, Matrix4, PerspectiveCamera, Vector3 } from "three";
2
2
 
3
3
  import { isDevEnvironment } from "../../engine/debug/debug.js";
4
4
  import { Gizmos } from "../../engine/engine_gizmos.js";
@@ -13,6 +13,8 @@ import { Behaviour } from "../Component.js";
13
13
  const debugParam = getParam("debugviewbox");
14
14
  const disabledGizmoColor = new RGBAColor(.5, .5, .5, .5);
15
15
 
16
+ export type ViewBoxMode = "continuous" | "once";
17
+
16
18
  /**
17
19
  * This component can be used to automatically fit a certain box area into the camera view - no matter your screen size or aspect ratio.
18
20
  * This is useful for example to frame a character or object in the center of the screen and ensure it is always fully visible. You can also animate or scale the viewbox to create zoom or framing effects.
@@ -36,6 +38,9 @@ const disabledGizmoColor = new RGBAColor(.5, .5, .5, .5);
36
38
  @registerType
37
39
  export class ViewBox extends Behaviour {
38
40
 
41
+ /**
42
+ * All active ViewBox instances. The last one in the array is the currently active one.
43
+ */
39
44
  static readonly instances: ViewBox[] = [];
40
45
 
41
46
  /**
@@ -45,21 +50,39 @@ export class ViewBox extends Behaviour {
45
50
  @serializable()
46
51
  referenceFieldOfView: number = -1;
47
52
 
53
+ /**
54
+ * The mode determines if the viewbox should be applied once or continuously while it is the active viewbox.
55
+ * Options:
56
+ * - "once": The viewbox will be applied once when it becomes the active viewbox. This is useful if you want to fit the view once and then allow the user to zoom or pan freely.
57
+ * - "continuous": The viewbox will be applied continuously while it is the active viewbox. This is useful if you animate or scale the viewbox over time.
58
+ */
59
+ @serializable()
60
+ get mode() { return this._mode; }
61
+ set mode(v: ViewBoxMode) {
62
+ if (v === this._mode) return;
63
+ this._mode = v;
64
+ if (v === "once") this._applyCount = 0;
65
+ if (debugParam || this.debug) console.debug("[ViewBox] Set mode:", v);
66
+ }
67
+ private _mode: ViewBoxMode = "continuous";
68
+
48
69
  /**
49
70
  * Enable debug logs and rendering for this component instance
50
71
  */
51
72
  @serializable()
52
73
  debug: boolean = false;
53
74
 
75
+ /** @internal */
54
76
  onEnable(): void {
55
77
  if (debugParam || this.debug || isDevEnvironment()) console.debug("[ViewBox] Using camera fov:", this.referenceFieldOfView);
56
78
  // register instance
57
79
  ViewBox.instances.push(this);
58
-
80
+ this._applyCount = 0;
59
81
  this.removeUpdateCallback();
60
82
  this.context.pre_render_callbacks.push(this.internalUpdate);
61
83
  }
62
84
 
85
+ /** @internal */
63
86
  onDisable(): void {
64
87
  if (debugParam || this.debug) console.debug("[ViewBox] Disabled");
65
88
  // unregister instance
@@ -77,6 +100,7 @@ export class ViewBox extends Behaviour {
77
100
 
78
101
  private static readonly _tempProjectionMatrix: Matrix4 = new Matrix4();
79
102
  private static readonly _tempProjectionMatrixInverse: Matrix4 = new Matrix4();
103
+ private _applyCount = 0;
80
104
 
81
105
  private internalUpdate = () => {
82
106
  if (this.context.isInXR) return;
@@ -108,6 +132,11 @@ export class ViewBox extends Behaviour {
108
132
  return;
109
133
  }
110
134
 
135
+ if (this._applyCount >= 1 && this.mode === "once") {
136
+ return;
137
+ }
138
+ this._applyCount++;
139
+
111
140
  const domWidth = this.context.domWidth;
112
141
  const domHeight = this.context.domHeight;
113
142
 
@@ -129,7 +158,7 @@ export class ViewBox extends Behaviour {
129
158
  ViewBox._tempProjectionMatrix.copy(camera.projectionMatrix);
130
159
  ViewBox._tempProjectionMatrixInverse.copy(camera.projectionMatrixInverse);
131
160
  const view = camera.view;
132
- const zoom = camera.zoom;
161
+ const cameraZoom = camera.zoom;
133
162
  const aspect = camera.aspect;
134
163
  const fov = camera.fov;
135
164
  // Set values to default so we can calculate the box size correctly
@@ -194,6 +223,7 @@ export class ViewBox extends Behaviour {
194
223
  width / diffWidth,
195
224
  height / diffHeight
196
225
  );
226
+ const zoom = scale / (height * .5);
197
227
  // console.log({ scale, width, height, boxWidth: boxWidth * camera.aspect, boxHeight, diffWidth, diffHeight, aspect: camera.aspect, distance })
198
228
  // this.context.focusRectSettings.zoom = 1.39;
199
229
  // if (!this.context.focusRect) this.context.setCameraFocusRect(this.context.domElement);
@@ -202,13 +232,13 @@ export class ViewBox extends Behaviour {
202
232
  vec.project(camera);
203
233
  this.context.focusRectSettings.offsetX = vec.x;
204
234
  this.context.focusRectSettings.offsetY = vec.y;
205
- this.context.focusRectSettings.zoom = scale / (height * .5);
235
+ this.context.focusRectSettings.zoom = zoom;
206
236
  // if we don't have a focus rect yet, set it to the dom element
207
237
  if (!this.context.focusRect) this.context.setCameraFocusRect(this.context.domElement);
208
238
 
209
239
  // Reset values
210
240
  camera.view = view;
211
- camera.zoom = zoom;
241
+ camera.zoom = cameraZoom;
212
242
  camera.aspect = aspect;
213
243
  camera.fov = fov;
214
244
  camera.projectionMatrix.copy(ViewBox._tempProjectionMatrix);