Newer
Older
import { objectTypeForOriginAndContentType } from "../object-types";
Brian Peiris
committed
import mediaHighlightFrag from "./media-highlight-frag.glsl";
const mediaAPIEndpoint = getReticulumFetchUrl("/api/v1/media");
const commonKnownContentTypes = {
gltf: "model/gltf",
glb: "model/gltf-binary",
png: "image/png",
jpg: "image/jpeg",
jpeg: "image/jpeg",
pdf: "application/pdf",
mp4: "video/mp4",
mp3: "audio/mpeg"
};
// thanks to https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
function b64EncodeUnicode(str) {
// first we use encodeURIComponent to get percent-encoded UTF-8, then we convert the percent-encodings
// into raw bytes which can be fed into btoa.
const CHAR_RE = /%([0-9A-F]{2})/g;
return btoa(encodeURIComponent(str).replace(CHAR_RE, (_, p1) => String.fromCharCode("0x" + p1)));
}
export const proxiedUrlFor = (url, index) => {
// farspark doesn't know how to read '=' base64 padding characters
const base64Url = b64EncodeUnicode(url).replace(/=+$/g, "");
// translate base64 + to - and / to _ for URL safety
const encodedUrl = base64Url.replace(/\+/g, "-").replace(/\//g, "_");
const method = index != null ? "extract" : "raw";
return `https://${process.env.FARSPARK_SERVER}/0/${method}/0/0/0/${index || 0}/${encodedUrl}`;
};
const resolveUrlCache = new Map();
export const resolveUrl = async (url, index) => {
const cacheKey = `${url}|${index}`;
if (resolveUrlCache.has(cacheKey)) return resolveUrlCache.get(cacheKey);
const resolved = await fetch(mediaAPIEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ media: { url, index } })
}).then(r => r.json());
export const guessContentType = url => {
const extension = new URL(url).pathname.split(".").pop();
return commonKnownContentTypes[extension];
};
export const upload = file => {
const formData = new FormData();
formData.append("media", file);
return fetch(mediaAPIEndpoint, {
method: "POST",
body: formData
}).then(r => r.json());
};
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// https://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side/32490603#32490603
function getOrientation(file, callback) {
const reader = new FileReader();
reader.onload = function(e) {
const view = new DataView(e.target.result);
if (view.getUint16(0, false) != 0xffd8) {
return callback(-2);
}
const length = view.byteLength;
let offset = 2;
while (offset < length) {
if (view.getUint16(offset + 2, false) <= 8) return callback(-1);
const marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xffe1) {
if (view.getUint32((offset += 2), false) != 0x45786966) {
return callback(-1);
}
const little = view.getUint16((offset += 6), false) == 0x4949;
offset += view.getUint32(offset + 4, little);
const tags = view.getUint16(offset, little);
offset += 2;
for (let i = 0; i < tags; i++) {
if (view.getUint16(offset + i * 12, little) == 0x0112) {
return callback(view.getUint16(offset + i * 12 + 8, little));
}
}
} else if ((marker & 0xff00) != 0xff00) {
break;
} else {
offset += view.getUint16(offset, false);
}
}
return callback(-1);
};
reader.readAsArrayBuffer(file);
}
let interactableId = 0;
export const addMedia = (src, template, contentOrigin, resolve = false, resize = false) => {
const scene = AFRAME.scenes[0];
entity.id = "interactable-media-" + interactableId++;
entity.setAttribute("media-loader", { resize, resolve, src: typeof src === "string" ? src : "" });
const orientation = new Promise(function(resolve) {
if (src instanceof File) {
getOrientation(src, x => {
resolve(x);
});
} else {
resolve(1);
}
});
if (src instanceof File) {
upload(src)
.then(response => {
const srcUrl = new URL(response.raw);
srcUrl.searchParams.set("token", response.meta.access_token);
entity.setAttribute("media-loader", { resolve: false, src: srcUrl.href });
})
.catch(() => {
entity.setAttribute("media-loader", { src: "error" });
});
if (contentOrigin) {
entity.addEventListener("media_resolved", ({ detail }) => {
const objectType = objectTypeForOriginAndContentType(contentOrigin, detail.contentType);
scene.emit("object_spawned", { objectType });
});
}
return { entity, orientation };
Brian Peiris
committed
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
export function injectCustomShaderChunks(obj) {
const vertexRegex = /\bbegin_vertex\b/;
const fragRegex = /\bgl_FragColor\b/;
const materialsSeen = new Set();
const shaderUniforms = [];
obj.traverse(object => {
if (!object.material || !["MeshStandardMaterial", "MeshBasicMaterial"].includes(object.material.type)) {
return;
}
object.material = object.material.clone();
object.material.onBeforeCompile = shader => {
if (!vertexRegex.test(shader.vertexShader)) return;
shader.uniforms.hubs_InteractorOneTransform = { value: [] };
shader.uniforms.hubs_InteractorTwoTransform = { value: [] };
shader.uniforms.hubs_InteractorTwoPos = { value: [] };
shader.uniforms.hubs_HighlightInteractorOne = { value: false };
shader.uniforms.hubs_HighlightInteractorTwo = { value: false };
shader.uniforms.hubs_Time = { value: 0 };
const vchunk = `
if (hubs_HighlightInteractorOne || hubs_HighlightInteractorTwo) {
vec4 wt = modelMatrix * vec4(transformed, 1);
// Used in the fragment shader below.
hubs_WorldPosition = wt.xyz;
}
`;
const vlines = shader.vertexShader.split("\n");
const vindex = vlines.findIndex(line => vertexRegex.test(line));
vlines.splice(vindex + 1, 0, vchunk);
vlines.unshift("varying vec3 hubs_WorldPosition;");
vlines.unshift("uniform bool hubs_HighlightInteractorOne;");
vlines.unshift("uniform bool hubs_HighlightInteractorTwo;");
shader.vertexShader = vlines.join("\n");
const flines = shader.fragmentShader.split("\n");
const findex = flines.findIndex(line => fragRegex.test(line));
flines.splice(findex + 1, 0, mediaHighlightFrag);
flines.unshift("varying vec3 hubs_WorldPosition;");
flines.unshift("uniform bool hubs_HighlightInteractorOne;");
flines.unshift("uniform mat4 hubs_InteractorOneTransform;");
flines.unshift("uniform bool hubs_HighlightInteractorTwo;");
flines.unshift("uniform mat4 hubs_InteractorTwoTransform;");
flines.unshift("uniform float hubs_Time;");
shader.fragmentShader = flines.join("\n");
if (!materialsSeen.has(object.material.uuid)) {
shaderUniforms.push(shader.uniforms);
materialsSeen.add(object.material.uuid);
}
};
object.material.needsUpdate = true;
});
return shaderUniforms;
}