@realsee/dnalogel
Version:
289 lines (288 loc) • 12.8 kB
JavaScript
var g = Object.defineProperty;
var I = (v, e, i) => e in v ? g(v, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : v[e] = i;
var t = (v, e, i) => (I(v, typeof e != "symbol" ? e + "" : e, i), i);
import { vertexShader as M, fragmentShader as P } from "./utils/shader.js";
import "hammerjs";
import { VideoTexture as F, LinearFilter as m, ShaderMaterial as y, Vector4 as x, SphereBufferGeometry as L, Mesh as V, Vector3 as l, Quaternion as b } from "three";
import "@realsee/five";
import "../vendor/@tweenjs/tween/dist/tween.esm.js.js";
import "../CSS3DRenderPlugin/utils/three/CSS3DRender.js";
import "../CSS3DRenderPlugin/utils/generateBehindFiveElement.js";
import { requestAnimationFrameInterval as A } from "../shared-utils/animationFrame/index.js";
import W from "./utils/index.js";
import E from "animejs";
import "../shared-utils/positionToVector3.js";
import "../CSS3DRenderPlugin/utils/three/CSS3DRenderer.js";
import "three/examples/jsm/renderers/CSS3DRenderer";
import "../CSS3DRenderPlugin/utils/getAllCSS3DObject.js";
import "../shared-utils/util.js";
import "../CSS3DRenderPlugin/utils/createResizeObserver.js";
import "../CSS3DRenderPlugin/utils/even.js";
import "../shared-utils/Subscribe.js";
import "../CSS3DRenderPlugin/utils/three/CSS3DObject.js";
import "../CSS3DRenderPlugin/utils/three/OpacityMesh.js";
import "../shared-utils/three/centerPoint.js";
import "../shared-utils/three/getObjectVisible.js";
import "../CSS3DRenderPlugin/utils/three/CSS3DScene.js";
import "../CSS3DRenderPlugin/utils/three/CSS3DGroup.js";
class ie {
/** 初始化视频、模型以及相关事件监听。 */
constructor(e, i) {
t(this, "_id");
/** 视频资源地址 */
t(this, "url");
/** Five Instance */
t(this, "five");
/** 视频对应的点位 */
t(this, "panoIndex");
/** 视频对应的 Work Observer */
t(this, "observer");
/** 视频在球面的 uv:[left, top, width, height] */
t(this, "origin");
/** Video DOM */
t(this, "video");
/** Video Mesh */
t(this, "videoMesh");
t(this, "_enabled", !0);
t(this, "_disposed", !1);
/** 视频资源是否满足播放条件 */
t(this, "videoDataLoaded", !1);
/** 视频是否曾经播放过 */
t(this, "hasVideoEverPlayed", !1);
/** 自动 render five 的销毁函数
* @remark 由于 render video 依赖 Five render,因此需要自动 render five。
*/
t(this, "renderFiveDisposer");
/** 把 uv 值转换成位置 */
t(this, "uv2Position", (e, i) => {
var c;
const o = Math.PI, a = Math.PI * 2, d = e, s = 1 - i, r = new l(
-1 * Math.cos(a * d) * Math.sin(o * s),
1 * Math.cos(o * s),
1 * Math.sin(a * d) * Math.sin(o * s)
);
r.setX(-r.x);
const h = (c = this.five.work) == null ? void 0 : c.observers[this.panoIndex];
if (!h)
return this.logWarning(`点位 ${this.panoIndex} 不存在,请检查 Five 数据是否正常。`);
const f = h.position.clone(), p = h.quaternion.clone(), u = r.clone();
return u.applyAxisAngle(new l(0, 1, 0), Math.PI / 2), u.applyQuaternion(p), u.add(f), u;
});
/** Five 数据加载后需要根据点位位姿调整点位模型位置 */
t(this, "onFiveDataLoaded", () => {
var r;
const e = (r = this.five.work) == null ? void 0 : r.observers[this.panoIndex];
if (!e)
return this.logWarning(`点位 ${this.panoIndex} 不存在,请检查 Five 数据是否正常。`);
this.observer = e;
const { x: i, y: n, z: o, w: a } = e.quaternion, d = new b(i, n, o, a), s = e.position;
this.videoMesh.position.fromArray([s.x, s.y, s.z]), this.videoMesh.quaternion.set(0, 0, 0, 1), this.videoMesh.rotateOnAxis(new l(0, 1, 0), Math.PI / 2), this.videoMesh.applyQuaternion(d);
});
/** 兼容视频播放 */
t(this, "onFiveWantsMoveToPano", (e) => {
e === this.panoIndex && (this.hasVideoEverPlayed || this.video.play().catch(() => {
}));
});
/** 走到某个点位上时,挂载/卸载视频 */
t(this, "onFivePanoArrived", (e) => {
if (this.disposed)
return this.logWarning("实例已被销毁");
this.enabled && this.panoIndex === e && this.mount();
});
t(this, "onFivePanoWillArrive", (e) => {
if (e !== this.panoIndex)
return this.unmount();
this.panoIndex !== e && this.unmount();
});
/** Five 模型变化 */
t(this, "onFiveModeChange", (e) => {
e !== "Panorama" && this.hide();
});
/** Five mode change 动画结束 */
t(this, "onFiveInitAnimationEnded", () => {
const e = this.five.getCurrentState();
e.mode === "Panorama" && e.panoIndex === this.panoIndex && this.mount();
});
/** Five Canvas 点击 */
t(this, "onFiveWantsTapGesture", (e) => {
if (!this.five.scene.children.includes(this.videoMesh))
return;
if (this.disposed)
return this.logWarning("实例已被销毁");
if (!this.enabled)
return this.logWarning("实例已被禁用");
const [i] = e.intersectObject(this.videoMesh);
if (!i)
return;
if (this.checkIntersectionInBounding(this.origin, i))
return this.video.muted = !this.video.muted, !1;
});
/** video canplaythrough 事件触发 */
t(this, "onVideoDataLoaded", () => {
this.videoDataLoaded = !0, this.video.paused === !1 && this.video.src !== "" && this.onVideoPlayingAndLoaded();
});
/** video playing 事件触发
* @remarks
* 事件触发时机:
* - video is ready to start after having been paused.
* - video is ready to play after delayed due to lack of data.
* @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video
*/
t(this, "onVideoPlaying", () => {
this.hasVideoEverPlayed = !0, this.video.src !== "" && this.videoDataLoaded && this.onVideoPlayingAndLoaded();
});
/** 触发 playing 和 canplaythrough */
t(this, "onVideoPlayingAndLoaded", () => {
if (!this.videoDataLoaded)
return this.logWarning("视频数据未加载");
if (this.video.paused === !0)
return this.logWarning("视频已暂停");
this.video.addEventListener(
"timeupdate",
() => {
if (this.video.src !== "" && this.panoIndex === this.five.getCurrentState().panoIndex && this.five.getCurrentState().mode === "Panorama" && (this.renderFiveDisposer || (this.renderFiveDisposer = A(() => {
this.five.needsRender = !0;
}, 30)), this.five.scene.add(this.videoMesh), this.videoMesh.material.uniforms.opacity.value === 0)) {
const e = {
value: 0
};
E({
targets: e,
value: 1,
duration: 300,
easing: "linear",
update: () => {
this.videoMesh.material.uniforms.opacity.value = e.value;
},
complete: () => {
this.videoMesh.material.uniforms.opacity.value = 1;
}
});
}
},
{ once: !0 }
);
});
/** video pause 事件触发 */
t(this, "onVideoPaused", () => {
var e;
(e = this.renderFiveDisposer) == null || e.call(this), this.renderFiveDisposer = void 0;
});
var r, h;
this.five = e, this.panoIndex = i.panoIndex, this._id = i.renderID, this.url = i.url, this.origin = i.origin.slice(), this._enabled = (h = (r = i.initialState) == null ? void 0 : r.enabled) != null ? h : !0;
const n = document.createElement("video");
n.crossOrigin = "anonymous", n.autoplay = !1, n.muted = !0, n.loop = !0, n.playsInline = !0, this.video = n;
const o = new F(this.video);
o.minFilter = m, o.magFilter = m;
const a = new y({
vertexShader: M,
fragmentShader: P,
uniforms: {
map: { value: o },
size: {
value: new x(this.origin[0], 1 - this.origin[1] - this.origin[3], this.origin[2], this.origin[3])
},
opacity: { value: 0 }
},
depthTest: !0,
transparent: !0
}), d = new L(1, 64, 64);
d.scale(-1, 1, 1);
const s = new V(d, a);
s.name = "pano-video", this.videoMesh = s, this.five.work && this.onFiveDataLoaded(), this.enabled && this.addEventListeners(), this.mountIfNeeded();
}
/** 传入的 ID,不可更改 */
get renderID() {
return this._id;
}
/** 是否已经销毁 */
get disposed() {
return this._disposed;
}
/** 是否已经启用 */
get enabled() {
return this._enabled;
}
/** 彻底销毁,不响应之后的任何事件 */
dispose() {
this.disposed || (this.disable(), this._disposed = !0);
}
enable() {
this.disposed || this.enabled || (this._enabled = !0, this.addEventListeners(), this.mountIfNeeded());
}
/** 禁用插件 */
disable() {
this.disposed || this.enabled && (this._enabled = !1, this.removeEventListeners(), this.unmount());
}
/** 切换到全景模式并看向视频
* @remark 如果遇到不能自动播放的问题,需要放到用户交互事件中调用。
*/
lookAtVideo() {
if (!this.observer)
return;
const e = this.getUVCenter();
if (!e)
return;
const i = this.uv2Position(e[0], e[1]);
if (!i)
return;
const n = new l().subVectors(i, this.observer.position).normalize(), { longitude: o, latitude: a } = W(n);
this.five.setState({ panoIndex: this.panoIndex, longitude: o, latitude: a }), this.onFiveWantsMoveToPano(this.panoIndex);
}
/** 获取视频中心点的 uv */
getUVCenter() {
if (!this.origin)
return;
const e = this.origin[0] + this.origin[2] / 2, i = this.origin[1] + this.origin[3] / 2;
return [e, i];
}
/** 添加时间监听 */
addEventListeners() {
this.five.on("loaded", this.onFiveDataLoaded), this.five.on("modeChange", this.onFiveModeChange), this.five.on("panoArrived", this.onFivePanoArrived), this.five.on("panoWillArrive", this.onFivePanoWillArrive), this.five.on("wantsMoveToPano", this.onFiveWantsMoveToPano), this.five.on("wantsTapGesture", this.onFiveWantsTapGesture), this.five.on("initAnimationEnded", this.onFiveInitAnimationEnded), this.video.addEventListener("canplaythrough", this.onVideoDataLoaded), this.video.addEventListener("playing", this.onVideoPlaying), this.video.addEventListener("pause", this.onVideoPaused);
}
/** 移除事件监听 */
removeEventListeners() {
this.five.off("loaded", this.onFiveDataLoaded), this.five.off("modeChange", this.onFiveModeChange), this.five.off("panoArrived", this.onFivePanoArrived), this.five.off("panoWillArrive", this.onFivePanoWillArrive), this.five.off("wantsMoveToPano", this.onFiveWantsMoveToPano), this.five.off("wantsTapGesture", this.onFiveWantsTapGesture), this.five.off("initAnimationEnded", this.onFiveInitAnimationEnded), this.video.removeEventListener("canplaythrough", this.onVideoDataLoaded), this.video.removeEventListener("playing", this.onVideoPlaying), this.video.removeEventListener("pause", this.onVideoPaused);
}
/** 如果满足 mount 条件,mount */
mountIfNeeded() {
const e = this.five.getCurrentState();
e.mode === "Panorama" && e.panoIndex === this.panoIndex && this.enabled && this.mount();
}
/** 挂载:加载视频资源;添加模型。 */
mount() {
if (this.disposed)
return this.logWarning("插件已经销毁,无法挂载。");
if (this.url === "")
return this.logWarning("视频资源不存在。");
this.video.src !== this.url && (this.video.src = this.url), this.video.paused && this.video.play().catch((e) => {
this.logWarning(e instanceof Error ? e.message : "视频播放失败。"), this.five.once("gesture", () => {
this.video.play().catch((i) => {
this.logWarning(i instanceof Error ? i.message : "自动播放视频失败。");
});
});
});
}
/** 卸载:销毁视频资源;移除模型。 */
unmount() {
this.video.pause(), this.videoMesh.material.uniforms.opacity.value = 0, this.video.src !== "" && (this.video.src = ""), this.five.scene.remove(this.videoMesh);
}
/** 暂停视频,去除模型。 */
hide() {
this.video.pause(), this.videoMesh.material.uniforms.opacity.value = 0, this.five.scene.remove(this.videoMesh);
}
/** 控制台打印警告 */
logWarning(e) {
console.warn("⛔ ==> [VideoMeshController]:", e);
}
/** 检测射线与模型的交点是不是在视频的范围内 */
checkIntersectionInBounding(e, i) {
if (!i.uv)
return !1;
const [n, o] = i.uv.toArray(), [a, d, s, r] = e, h = 1 - d - r, f = a + s, p = h + r;
return n >= a && n <= f && o >= h && o <= p;
}
}
export {
ie as VideoMeshController
};