@@ -486,8 +486,8 @@
|
|
486
486
|
}
|
487
487
|
// console.trace("INTERNAL ENABLE");
|
488
488
|
this.__didEnable = true;
|
489
|
+
this.__isEnabled = true;
|
489
490
|
this.onEnable();
|
490
|
-
this.__isEnabled = true;
|
491
491
|
return true;
|
492
492
|
}
|
493
493
|
|
@@ -501,8 +501,8 @@
|
|
501
501
|
return;
|
502
502
|
}
|
503
503
|
this.__didEnable = false;
|
504
|
+
this.__isEnabled = false;
|
504
505
|
this.onDisable();
|
505
|
-
this.__isEnabled = false;
|
506
506
|
}
|
507
507
|
|
508
508
|
/** @internal */
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { getParam, resolveUrl } from "../engine/engine_utils";
|
2
2
|
import { SerializationContext, TypeSerializer } from "./engine_serialization_core";
|
3
3
|
import { Context } from "./engine_setup";
|
4
|
-
import { Group, Object3D, Texture } from "three";
|
4
|
+
import { Group, Object3D, Texture, TextureLoader } from "three";
|
5
5
|
import { processNewScripts } from "./engine_mainloop_utils";
|
6
6
|
import { registerPrefabProvider, syncInstantiate } from "./engine_networking_instantiate";
|
7
7
|
import { download } from "./engine_web_api";
|
@@ -372,15 +372,19 @@
|
|
372
372
|
return img;
|
373
373
|
}
|
374
374
|
|
375
|
+
private loader: TextureLoader | null = null;
|
375
376
|
createTexture(): Promise<Texture | null> {
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
377
|
+
if (!this.loader) this.loader = new TextureLoader();
|
378
|
+
this.loader.setCrossOrigin("anonymous");
|
379
|
+
return this.loader.loadAsync(this.url);
|
380
|
+
// return this.getBitmap().then((bitmap) => {
|
381
|
+
// if (bitmap) {
|
382
|
+
// const texture = new Texture(bitmap);
|
383
|
+
// texture.needsUpdate = true;
|
384
|
+
// return texture;
|
385
|
+
// }
|
386
|
+
// return null;
|
387
|
+
// });
|
384
388
|
}
|
385
389
|
|
386
390
|
/** Loads the bitmap data of the image */
|
@@ -253,13 +253,14 @@
|
|
253
253
|
}
|
254
254
|
loadingBarContainer.appendChild(logo);
|
255
255
|
|
256
|
+
|
256
257
|
this._loadingBar = document.createElement("div");
|
257
258
|
loadingBarContainer.appendChild(this._loadingBar);
|
258
259
|
const getGradientPos = function (t: number): string {
|
259
260
|
return Mathf.lerp(maxWidth * .5, 100 - maxWidth * .5, t) + "%";
|
260
261
|
}
|
261
262
|
this._loadingBar.style.background =
|
262
|
-
|
263
|
+
`linear-gradient(90deg, #02022B ${getGradientPos(0)}, #0BA398 ${getGradientPos(.4)}, #99CC33 ${getGradientPos(.5)}, #D7DB0A ${getGradientPos(1)})`;
|
263
264
|
this._loadingBar.style.backgroundAttachment = "fixed";
|
264
265
|
this._loadingBar.style.width = "0%";
|
265
266
|
this._loadingBar.style.height = "100%";
|
@@ -301,6 +302,14 @@
|
|
301
302
|
}
|
302
303
|
}
|
303
304
|
|
305
|
+
if (!hasLicense) {
|
306
|
+
const nonCommercialContainer = document.createElement("div");
|
307
|
+
nonCommercialContainer.style.paddingTop = ".6em";
|
308
|
+
nonCommercialContainer.style.fontSize = ".8em";
|
309
|
+
nonCommercialContainer.innerText = "NON COMMERCIAL";
|
310
|
+
this._loadingElement.appendChild(nonCommercialContainer);
|
311
|
+
}
|
312
|
+
|
304
313
|
return this._loadingElement;
|
305
314
|
}
|
306
315
|
}
|
@@ -30,22 +30,18 @@
|
|
30
30
|
}
|
31
31
|
|
32
32
|
|
33
|
-
const _licenseText = "🌵 <span class=\"text\">Made with <a href=\"https://needle.tools\" target=\"_blank\">Needle</a></span>";
|
34
33
|
const licenseElementIdentifier = "needle-license-element";
|
35
34
|
const licenseDuration = 30000;
|
36
35
|
const licenseDelay = 600;
|
37
36
|
|
38
37
|
function onNonCommercialVersionDetected(ctx: IContext) {
|
39
|
-
insertNonCommercialUseHint(ctx);
|
40
|
-
|
38
|
+
setTimeout(() => insertNonCommercialUseHint(ctx), 2000);
|
39
|
+
sendUsageMessageToAnalyticsBackend();
|
41
40
|
}
|
42
41
|
|
43
42
|
function insertNonCommercialUseHint(ctx: IContext) {
|
44
43
|
|
45
|
-
let licenseText = _licenseText;
|
46
44
|
const licenseElement = createLicenseElement();
|
47
|
-
licenseElement.innerHTML = licenseText;
|
48
|
-
|
49
45
|
const style = createLicenseStyle();
|
50
46
|
|
51
47
|
const interval = setInterval(() => {
|
@@ -59,10 +55,22 @@
|
|
59
55
|
logNonCommercialUse();
|
60
56
|
|
61
57
|
let svg = `<img class="logo" src="${logoSVG}" style="width: 40px; height: 40px; margin-right: 2px; vertical-align: middle; margin-bottom: 2px;"/>`;
|
62
|
-
|
63
|
-
|
64
|
-
|
58
|
+
const logoElement = document.createElement("div");
|
59
|
+
logoElement.innerHTML = svg;
|
60
|
+
logoElement.classList.add("logo");
|
61
|
+
licenseElement.appendChild(logoElement);
|
65
62
|
|
63
|
+
const textElement = document.createElement("div");
|
64
|
+
textElement.classList.add("text");
|
65
|
+
textElement.innerHTML = "Needle Engine<br/><span class=\"non-commercial\">Non Commercial</span>";
|
66
|
+
licenseElement.appendChild(textElement);
|
67
|
+
|
68
|
+
licenseElement.title = "Needle Engine — non commercial version";
|
69
|
+
licenseElement.addEventListener("click", () => {
|
70
|
+
console.log("CLICK")
|
71
|
+
globalThis.open("https://needle.tools", "_blank");
|
72
|
+
});
|
73
|
+
|
66
74
|
const removeDelay = licenseDuration + licenseDelay;
|
67
75
|
setTimeout(() => {
|
68
76
|
clearInterval(interval);
|
@@ -110,30 +118,66 @@
|
|
110
118
|
${selector} {
|
111
119
|
font-family: 'Roboto', sans-serif !important;
|
112
120
|
font-weight: 300;
|
121
|
+
transition: all 0.1s ease-in-out !important;
|
122
|
+
pointer-events: all;
|
113
123
|
}
|
114
124
|
|
125
|
+
${selector}:hover {
|
126
|
+
cursor: pointer;
|
127
|
+
transition: all 0.1s ease-in-out !important;
|
128
|
+
}
|
129
|
+
|
115
130
|
${selector}, ${selector} > * {
|
116
131
|
display: inline-block !important;
|
117
132
|
visibility: visible !important;
|
118
133
|
background: none !important;
|
119
134
|
border: none !important;
|
120
135
|
text-decoration: none !important;
|
136
|
+
vertical-align: middle !important;
|
121
137
|
}
|
138
|
+
|
139
|
+
@keyframes license-animation {
|
140
|
+
1% {
|
141
|
+
opacity: 0;
|
142
|
+
}
|
143
|
+
2.5% {
|
144
|
+
opacity: 1;
|
145
|
+
}
|
146
|
+
98% {
|
147
|
+
opacity: 1;
|
148
|
+
}
|
149
|
+
99% {
|
150
|
+
opacity: 0;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
${selector} .text {
|
154
|
+
opacity: 0;
|
155
|
+
animation: license-animation;
|
156
|
+
animation-iteration-count: 1;
|
157
|
+
animation-duration: ${(licenseDuration / 1000)}s;
|
158
|
+
animation-delay: ${licenseDelay / 1000}s;
|
159
|
+
animation-easing: ease-in-out;
|
160
|
+
mix-blend-mode: difference;
|
161
|
+
color: rgb(40, 40, 40);
|
162
|
+
mix-blend-mode: difference;
|
163
|
+
line-height: 1em;
|
164
|
+
margin-left: -3px;
|
165
|
+
}
|
122
166
|
|
123
|
-
${selector}
|
124
|
-
|
125
|
-
font-weight:
|
167
|
+
${selector} .text .non-commercial {
|
168
|
+
font-size: 0.8em;
|
169
|
+
font-weight: 600;
|
170
|
+
text-transform: uppercase;
|
126
171
|
}
|
127
172
|
|
128
|
-
@keyframes
|
173
|
+
@keyframes logo-animation {
|
129
174
|
0% {
|
130
175
|
transform: translate(0px, 10px);
|
131
|
-
opacity: 0;
|
132
176
|
pointer-events: none;
|
133
177
|
}
|
134
178
|
1% {
|
135
179
|
transform: translate(0, -5px);
|
136
|
-
opacity:
|
180
|
+
opacity: 1;
|
137
181
|
}
|
138
182
|
2% {
|
139
183
|
transform: translate(0, 2.5px);
|
@@ -141,7 +185,6 @@
|
|
141
185
|
3% {
|
142
186
|
transform: translate(0, 0px);
|
143
187
|
pointer-events: all;
|
144
|
-
opacity: 1;
|
145
188
|
}
|
146
189
|
4% {
|
147
190
|
transform: scale(1)
|
@@ -161,14 +204,15 @@
|
|
161
204
|
transform: scale(1)
|
162
205
|
}
|
163
206
|
100% {
|
207
|
+
pointer-events: none;
|
164
208
|
opacity: 0;
|
165
|
-
pointer-events: none;
|
166
209
|
}
|
167
210
|
}
|
168
|
-
|
211
|
+
|
212
|
+
${selector} .logo {
|
169
213
|
opacity: 0;
|
170
214
|
pointer-events: none;
|
171
|
-
animation:
|
215
|
+
animation: logo-animation;
|
172
216
|
animation-iteration-count: 1;
|
173
217
|
animation-duration: ${(licenseDuration / 1000)}s;
|
174
218
|
animation-delay: ${licenseDelay / 1000}s;
|
@@ -184,8 +228,9 @@
|
|
184
228
|
transition: all 0.1s ease-in-out !important;
|
185
229
|
}
|
186
230
|
|
187
|
-
${selector} .logo
|
188
|
-
|
231
|
+
${selector}:hover .logo {
|
232
|
+
transition: all 0.1s ease-in-out !important;
|
233
|
+
transform: scale(1.1) !important;
|
189
234
|
cursor: pointer !important;
|
190
235
|
}
|
191
236
|
`
|
@@ -193,7 +238,7 @@
|
|
193
238
|
}
|
194
239
|
|
195
240
|
|
196
|
-
async function
|
241
|
+
async function sendUsageMessageToAnalyticsBackend() {
|
197
242
|
try {
|
198
243
|
const analyticsBackendUrlForward = "https://urls.needle.tools/analytics-endpoint";
|
199
244
|
const res = await fetch(analyticsBackendUrlForward);
|
@@ -207,7 +252,8 @@
|
|
207
252
|
|
208
253
|
let endpoint = "api/v1/register/web-request";
|
209
254
|
if (!analyticsUrl.endsWith("/")) endpoint = "/" + endpoint;
|
210
|
-
const
|
255
|
+
const type = hasProLicense() ? "commercial" : "non-commercial";
|
256
|
+
const finalUrl = `${analyticsUrl}${endpoint}?type=${type}&url=${encodeURIComponent(currentUrl)}&hostname=${encodeURIComponent(window.location.hostname)}&pathname=${encodeURIComponent(window.location.pathname)}&search=${encodeURIComponent(window.location.search)}&hash=${encodeURIComponent(window.location.hash)}`;
|
211
257
|
if (debug) console.log("Sending non-commercial usage message to analytics backend", finalUrl);
|
212
258
|
|
213
259
|
|
@@ -362,7 +362,7 @@
|
|
362
362
|
}
|
363
363
|
|
364
364
|
onDeserialize(data: string, _context: SerializationContext) {
|
365
|
-
if (typeof data === "string") {
|
365
|
+
if (typeof data === "string" && data.length > 0) {
|
366
366
|
return resolveUrl(_context.gltfId, data);
|
367
367
|
}
|
368
368
|
return undefined;
|
@@ -40,6 +40,13 @@
|
|
40
40
|
|
41
41
|
export class ScreenCapture extends Behaviour implements IPointerClickHandler {
|
42
42
|
|
43
|
+
onPointerEnter() {
|
44
|
+
this.context.input.setCursorPointer();
|
45
|
+
}
|
46
|
+
onPointerExit() {
|
47
|
+
this.context.input.setCursorNormal();
|
48
|
+
}
|
49
|
+
|
43
50
|
onPointerClick(evt : PointerEventData) {
|
44
51
|
if(evt && evt.pointerId !== 0) return;
|
45
52
|
if(this.context.connection.isInRoom === false) return;
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import { Behaviour, GameObject } from "./Component";
|
2
|
-
import * as THREE from "three";
|
3
2
|
import { serializable } from "../engine/engine_serialization_decorator";
|
4
|
-
import {
|
3
|
+
import { Material, Mesh, Object3D, ShaderMaterial, sRGBEncoding, Texture, Vector2, Vector4, VideoTexture } from "three";
|
5
4
|
import { awaitInput } from "../engine/engine_input_utils";
|
6
|
-
import { getParam
|
5
|
+
import { getParam } from "../engine/engine_utils";
|
7
6
|
import { Renderer } from "./Renderer";
|
8
7
|
import { getWorldScale } from "../engine/engine_three_utils";
|
9
8
|
import { ObjectUtils, PrimitiveType } from "../engine/engine_create_objects";
|
10
9
|
import { Context } from "../engine/engine_setup";
|
10
|
+
import { isDevEnvironment } from "../engine/debug";
|
11
11
|
|
12
12
|
const debug = getParam("debugvideo");
|
13
13
|
|
@@ -46,7 +46,7 @@
|
|
46
46
|
export class VideoPlayer extends Behaviour {
|
47
47
|
|
48
48
|
@serializable(Object3D)
|
49
|
-
renderer:
|
49
|
+
renderer: Object3D | null = null;
|
50
50
|
@serializable()
|
51
51
|
playOnAwake: boolean = true;
|
52
52
|
|
@@ -149,18 +149,29 @@
|
|
149
149
|
}
|
150
150
|
private _muted: boolean = false;
|
151
151
|
|
152
|
+
@serializable()
|
153
|
+
private set audioOutputMode(mode: VideoAudioOutputMode) {
|
154
|
+
if (mode !== this._audioOutputMode) {
|
155
|
+
if (mode === VideoAudioOutputMode.AudioSource && isDevEnvironment()) console.warn("VideoAudioOutputMode.AudioSource is not yet implemented");
|
156
|
+
this._audioOutputMode = mode;
|
157
|
+
this.updateVideoElementSettings();
|
158
|
+
}
|
159
|
+
}
|
160
|
+
private get audioOutputMode() { return this._audioOutputMode; }
|
161
|
+
|
162
|
+
private _audioOutputMode: VideoAudioOutputMode = VideoAudioOutputMode.Direct;
|
163
|
+
|
152
164
|
/** Set this to false to pause video playback while the tab is not active */
|
153
165
|
playInBackground: boolean = true;
|
154
166
|
|
155
167
|
private _crossOrigin: string | null = "anonymous";
|
156
168
|
|
157
|
-
private audioOutputMode: VideoAudioOutputMode = VideoAudioOutputMode.AudioSource;
|
158
169
|
|
159
170
|
private source!: VideoSource;
|
160
171
|
private url?: string | null = null;
|
161
172
|
|
162
173
|
private _videoElement: HTMLVideoElement | null = null;
|
163
|
-
private _videoTexture:
|
174
|
+
private _videoTexture: VideoTexture | null = null;
|
164
175
|
private _videoMaterial: Material | null = null;
|
165
176
|
|
166
177
|
private _isPlaying: boolean = false;
|
@@ -195,27 +206,11 @@
|
|
195
206
|
}
|
196
207
|
}
|
197
208
|
|
198
|
-
|
199
|
-
|
209
|
+
onEnable(): void {
|
210
|
+
window.addEventListener('visibilitychange', this.visibilityChanged);
|
200
211
|
|
201
|
-
window.addEventListener('visibilitychange', _evt => {
|
202
|
-
switch (document.visibilityState) {
|
203
|
-
case "hidden":
|
204
|
-
if(!this.playInBackground){
|
205
|
-
this.wasPlaying = this._isPlaying;
|
206
|
-
this.pause();
|
207
|
-
}
|
208
|
-
break;
|
209
|
-
case "visible":
|
210
|
-
if (this.wasPlaying && !this._isPlaying) this.play();
|
211
|
-
break;
|
212
|
-
}
|
213
|
-
});
|
214
|
-
}
|
215
|
-
|
216
|
-
onEnable(): void {
|
217
212
|
if (this.playOnAwake === true) {
|
218
|
-
this.
|
213
|
+
this.create(true);
|
219
214
|
}
|
220
215
|
if (this.screenspace) {
|
221
216
|
this._overlay?.start();
|
@@ -224,9 +219,24 @@
|
|
224
219
|
}
|
225
220
|
|
226
221
|
onDisable(): void {
|
222
|
+
window.removeEventListener('visibilitychange', this.visibilityChanged);
|
227
223
|
this.pause();
|
228
224
|
}
|
229
225
|
|
226
|
+
private visibilityChanged = (_: Event) => {
|
227
|
+
switch (document.visibilityState) {
|
228
|
+
case "hidden":
|
229
|
+
if (!this.playInBackground) {
|
230
|
+
this.wasPlaying = this._isPlaying;
|
231
|
+
this.pause();
|
232
|
+
}
|
233
|
+
break;
|
234
|
+
case "visible":
|
235
|
+
if (this.wasPlaying && !this._isPlaying) this.play();
|
236
|
+
break;
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
230
240
|
onDestroy(): void {
|
231
241
|
if (this._videoElement) {
|
232
242
|
this._videoElement.parentElement?.removeChild(this._videoElement);
|
@@ -258,12 +268,15 @@
|
|
258
268
|
}
|
259
269
|
|
260
270
|
play() {
|
271
|
+
if (!this._videoElement) this.create(false);
|
261
272
|
if (!this._videoElement) return;
|
262
273
|
if (this._isPlaying && !this._videoElement?.ended && !this._videoElement?.paused) return;
|
263
274
|
this._isPlaying = true;
|
264
275
|
if (!this._receivedInput) this._videoElement.muted = true;
|
265
276
|
this.updateVideoElementSettings();
|
266
|
-
this._videoElement
|
277
|
+
this._videoElement.currentTime = this.time;
|
278
|
+
this._videoElement.play().catch(err => {
|
279
|
+
console.log(err);
|
267
280
|
// https://developer.chrome.com/blog/play-request-was-interrupted/
|
268
281
|
if (debug)
|
269
282
|
console.error("Error playing video", err, "CODE=" + err.code, this.videoElement?.src, this);
|
@@ -272,19 +285,23 @@
|
|
272
285
|
this.play();
|
273
286
|
}, 1000);
|
274
287
|
});
|
275
|
-
if (debug) console.log("play", this._videoElement);
|
288
|
+
if (debug) console.log("play", this._videoElement, this.time);
|
276
289
|
}
|
277
290
|
|
278
291
|
stop() {
|
279
292
|
this._isPlaying = false;
|
293
|
+
this.time = 0;
|
280
294
|
if (!this._videoElement) return;
|
281
295
|
this._videoElement.currentTime = 0;
|
282
296
|
this._videoElement.pause();
|
297
|
+
if (debug) console.log("STOP", this);
|
283
298
|
}
|
284
299
|
|
285
300
|
pause(): void {
|
301
|
+
this.time = this._videoElement?.currentTime ?? 0;
|
286
302
|
this._isPlaying = false;
|
287
303
|
this._videoElement?.pause();
|
304
|
+
if (debug) console.log("PAUSE", this, this.currentTime);
|
288
305
|
}
|
289
306
|
|
290
307
|
|
@@ -301,30 +318,36 @@
|
|
301
318
|
|
302
319
|
if (!src) return;
|
303
320
|
|
304
|
-
// console.log(src, this);
|
305
321
|
|
306
322
|
if (!this._videoElement) {
|
323
|
+
if (debug)
|
324
|
+
console.warn("Create VideoElement", this);
|
307
325
|
this._videoElement = this.createVideoElement();
|
308
326
|
this.context.domElement?.prepend(this._videoElement);
|
309
327
|
// hide it because otherwise it would overlay the website with default css
|
310
328
|
this.updateVideoElementStyles();
|
311
329
|
}
|
330
|
+
|
312
331
|
if (typeof src === "string") {
|
332
|
+
if (debug) console.log("Set Video src", src);
|
313
333
|
this._videoElement.src = src;
|
314
|
-
|
315
|
-
|
334
|
+
// Nor sure why we did this here, but with this code the video does not restart when being paused / enable toggled
|
335
|
+
// const str = this._videoElement["captureStream"]?.call(this._videoElement);
|
336
|
+
// this.clip = str;
|
316
337
|
}
|
317
|
-
else
|
338
|
+
else {
|
339
|
+
if (debug) console.log("Set Video srcObject", src);
|
318
340
|
this._videoElement.srcObject = src;
|
341
|
+
}
|
319
342
|
|
320
343
|
|
321
344
|
if (!this._videoTexture)
|
322
|
-
this._videoTexture = new
|
345
|
+
this._videoTexture = new VideoTexture(this._videoElement);
|
323
346
|
this._videoTexture.flipY = false;
|
324
|
-
this._videoTexture.encoding =
|
347
|
+
this._videoTexture.encoding = sRGBEncoding;
|
325
348
|
this.handleBeginPlaying(playAutomatically);
|
326
349
|
if (debug)
|
327
|
-
console.log(this);
|
350
|
+
console.log(this, playAutomatically);
|
328
351
|
}
|
329
352
|
|
330
353
|
updateAspect() {
|
@@ -354,7 +377,7 @@
|
|
354
377
|
const video = document.createElement("video") as HTMLVideoElement;
|
355
378
|
if (this._crossOrigin)
|
356
379
|
video.setAttribute("crossorigin", this._crossOrigin);
|
357
|
-
if (debug) console.log("
|
380
|
+
if (debug) console.log("created video element", video);
|
358
381
|
return video;
|
359
382
|
}
|