Needle Engine

Changes between version 3.45.1-beta.2 and 3.45.1-beta.3
Files changed (3) hide show
  1. src/engine/engine_three_utils.ts +2 -0
  2. src/engine-components/GroundProjection.ts +106 -7
  3. src/engine/webcomponents/needle-button.ts +8 -6
src/engine/engine_three_utils.ts CHANGED
@@ -89,6 +89,7 @@
89
89
  */
90
90
  export function getTempVector(vecOrX?: Vector3 | number | DOMPointReadOnly, y?: number, z?: number) {
91
91
  const vec = _tempVecs.get();
92
+ vec.set(0, 0, 0); // initialize with default values
92
93
  if (vecOrX instanceof Vector3) vec.copy(vecOrX);
93
94
  else if (vecOrX instanceof DOMPointReadOnly) vec.set(vecOrX.x, vecOrX.y, vecOrX.z);
94
95
  else {
@@ -108,6 +109,7 @@
108
109
  */
109
110
  export function getTempQuaternion(value?: Quaternion | DOMPointReadOnly) {
110
111
  const val = _tempQuats.get();
112
+ val.identity();
111
113
  if (value instanceof Quaternion) val.copy(value);
112
114
  else if (value instanceof DOMPointReadOnly) val.set(value.x, value.y, value.z, value.w);
113
115
  return val;
src/engine-components/GroundProjection.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Texture, Vector3 } from "three";
1
+ import { Material, MeshBasicMaterial, ShaderMaterial, Texture, Vector3 } from "three";
2
2
  import { GroundedSkybox as GroundProjection } from 'three/examples/jsm/objects/GroundedSkybox.js';
3
3
 
4
4
  import { serializable } from "../engine/engine_serialization_decorator.js";
@@ -88,6 +88,10 @@
88
88
  if (this._projection && this.scene.backgroundRotation) {
89
89
  this._projection.rotation.copy(this.scene.backgroundRotation);
90
90
  }
91
+ const mat = this._projection?.material as unknown as ShaderMaterial;
92
+ if (mat?.uniforms?.blurriness && this.context.scene.backgroundBlurriness !== undefined && mat.uniforms.blurriness.value != this.context.scene.backgroundBlurriness) {
93
+ mat.uniforms.blurriness.value = this.context.scene.backgroundBlurriness;
94
+ }
91
95
  }
92
96
 
93
97
  private updateAndCreate() {
@@ -95,6 +99,9 @@
95
99
  this._watcher?.apply();
96
100
  }
97
101
 
102
+ /**
103
+ * Updates the ground projection. This is called automatically when the environment changes.
104
+ */
98
105
  updateProjection() {
99
106
  if (!this.context.scene.environment || this.context.xr?.isPassThrough) {
100
107
  this._projection?.removeFromParent();
@@ -107,6 +114,29 @@
107
114
  this._projection?.removeFromParent();
108
115
  this._projection = new GroundProjection(this.context.scene.environment, this._height, this.radius);
109
116
  this._projection.position.y = this._height - offset;
117
+ const originalMaterial = this._projection.material as MeshBasicMaterial;
118
+
119
+ // const mat = this._projection.material;
120
+ const customShader = new ShaderMaterial({
121
+ uniforms: {
122
+ map: { value: this.context.scene.environment },
123
+ blurriness: { value: this.context.scene.backgroundBlurriness }
124
+ },
125
+ vertexShader: vertexShader,
126
+ fragmentShader: fragmentShader
127
+ });
128
+ customShader.depthWrite = false;
129
+ this._projection.material = customShader as unknown as MeshBasicMaterial;
130
+ // TODO: Blit the result of this shader once to a texture and use that texture for the projection
131
+
132
+ // setInterval(() => {
133
+ // if (this._projection?.material === originalMaterial) {
134
+ // this._projection.material = customShader as unknown as MeshBasicMaterial;
135
+ // }
136
+ // else {
137
+ // this._projection.material = originalMaterial;
138
+ // }
139
+ // }, 1000)
110
140
  }
111
141
 
112
142
  this._lastEnvironment = this.context.scene.environment;
@@ -120,11 +150,13 @@
120
150
  this._projection.updateWorldMatrix(true, true);
121
151
  const scenebounds = getBoundingBox(this.context.scene.children, [this._projection]);
122
152
  let floor_y = scenebounds.min.y;
123
- const wp = getWorldPosition(this._projection);
124
- wp.x = scenebounds.min.x + (scenebounds.max.x - scenebounds.min.x) * .5;
125
- wp.y = floor_y + this._height - offset;
126
- wp.z = scenebounds.min.z + (scenebounds.max.z - scenebounds.min.z) * .5;
127
- setWorldPosition(this._projection, wp);
153
+ if (floor_y < Infinity) {
154
+ const wp = getWorldPosition(this._projection);
155
+ wp.x = scenebounds.min.x + (scenebounds.max.x - scenebounds.min.x) * .5;
156
+ wp.y = floor_y + this._height - offset;
157
+ wp.z = scenebounds.min.z + (scenebounds.max.z - scenebounds.min.z) * .5;
158
+ setWorldPosition(this._projection, wp);
159
+ }
128
160
  }
129
161
 
130
162
  /* TODO realtime adjustments aren't possible anymore with GroundedSkybox (mesh generation)
@@ -139,4 +171,71 @@
139
171
  }
140
172
  }
141
173
 
142
- }
174
+ }
175
+
176
+
177
+ const vertexShader = `
178
+ varying vec2 vUv;
179
+
180
+ void main() {
181
+ vUv = uv;
182
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
183
+ }
184
+ `;
185
+
186
+ // Fragment Shader
187
+ const fragmentShader = `
188
+ uniform sampler2D map;
189
+ uniform float blurriness;
190
+ varying vec2 vUv;
191
+
192
+ const float PI = 3.14159265359;
193
+
194
+ // Gaussian function
195
+ float gaussian(float x, float sigma) {
196
+ return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * PI) * sigma);
197
+ }
198
+
199
+ // Custom smoothstep function for desired falloff
200
+ float customSmoothstep(float edge0, float edge1, float x) {
201
+ float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
202
+ return t * t * (3.0 - 2.0 * t);
203
+ }
204
+
205
+ void main() {
206
+ vec2 center = vec2(0.0, 0.0);
207
+ vec2 pos = vUv;
208
+ pos.x = 0.0; // Only consider vertical distance
209
+ float distance = length(pos - center) * 2.0;
210
+
211
+ // Calculate blur amount based on custom falloff
212
+ float blurAmount = customSmoothstep(0.4, 1.0, distance);
213
+ blurAmount = clamp(blurAmount, 0.0, 1.0); // Ensure blur amount is within valid range
214
+
215
+ // Gaussian blur
216
+ vec2 pixelSize = 1.0 / vec2(textureSize(map, 0));
217
+ vec4 color = vec4(0.0);
218
+ float totalWeight = 0.0;
219
+ int blurSize = int(20.0 * min(1.0, blurriness) * blurAmount); // Adjust blur size based on distance and blurriness
220
+ float lodLevel = log2(float(blurSize)) * 0.5; // Compute LOD level
221
+
222
+ for (int x = -blurSize; x <= blurSize; x++) {
223
+ for (int y = -blurSize; y <= blurSize; y++) {
224
+ vec2 offset = vec2(float(x), float(y)) * pixelSize * blurAmount;
225
+ float weight = gaussian(length(vec2(float(x), float(y))), 1000.0 * blurAmount); // Use a fixed sigma value
226
+ color += textureLod(map, vUv + offset, lodLevel) * weight;
227
+ totalWeight += weight;
228
+ }
229
+ }
230
+
231
+ color = totalWeight > 0.0 ? color / totalWeight : texture2D(map, vUv);
232
+
233
+ gl_FragColor = color;
234
+
235
+ #include <tonemapping_fragment>
236
+ #include <colorspace_fragment>
237
+
238
+ // Uncomment to visualize blur amount
239
+ // gl_FragColor = vec4(blurAmount, 0.0, 0.0, 1.0);
240
+ }
241
+ `;
src/engine/webcomponents/needle-button.ts CHANGED
@@ -37,6 +37,12 @@
37
37
 
38
38
  static observedAttributes = ["ar", "vr", "quicklook"];
39
39
 
40
+ constructor() {
41
+ super();
42
+ this.removeEventListener("click", this.#onclick);
43
+ this.addEventListener("click", this.#onclick);
44
+ }
45
+
40
46
  attributeChangedCallback(_name: string, _oldValue: string, _newValue: string) {
41
47
  this.#update()
42
48
  }
@@ -147,20 +153,16 @@
147
153
 
148
154
  #updateVisibility() {
149
155
  if (this.#button) {
150
- // if the user has set any display style don't override it
151
- if (this.style.display?.length) {
152
- return;
153
- }
154
156
  if (this.#button.style.display === "none") {
155
157
  this.style.display = "none";
156
158
  }
157
- else {
159
+ else if (this.style.display === "none") {
158
160
  this.style.display = "";
159
161
  }
160
162
  }
161
163
  }
162
164
 
163
- override onclick = (_ev: MouseEvent) => {
165
+ #onclick = (_ev: MouseEvent) => {
164
166
  if (isDev) {
165
167
  console.log("Needle Button clicked")
166
168
  }