Hello ,
I’m encountering an issue while integrating Three.js(loading .obj and .mtl file) with krpano, and I’m seeking some guidance or insights from the community.
Problem Description : A black plane is appearing in the center of the screen as shown in the image, which seems to interfere with the visibility of other objects.
I found that setting the camera’s z-axis to a value other than zero resolves the black plane appearance issue that I don’t want to do.
Any guidance or pointers in the right direction would be greatly appreciated.
Below is the plugin code attached…
/*
krpano ThreeJS example plugin
- use three.js inside krpano
- with stereo-rendering and WebVR support
- with 3d object hit-testing (onover, onout, onup, ondown, onclick) and mouse cursor handling
*/
function krpanoplugin() {
var local = this;
var krpano = null;
var device = null;
var plugin = null;
this.registerplugin = function (krpanointerface, pluginpath, pluginobject) {
krpano = krpanointerface;
device = krpano.device;
plugin = pluginobject;
if (krpano.version < "1.19") {
krpano.trace(3, "ThreeJS plugin - too old krpano version (min. 1.19)");
return;
}
if (!device.webgl) {
// show warning
krpano.trace(2, "ThreeJS plugin - WebGL required");
return;
}
krpano.debugmode = true;
krpano.trace(0, "ThreeJS krpano plugin");
// load the requiered three.js scripts
// load_scripts(["three.min.js"], start);
load_scripts(["three.js", "MTLLoader.js", "OBJLoader.js"], start);
}
local.unloadplugin = function () {
// no unloading support at the moment
plugin = null;
krpano = null;
}
local.onresize = function (width, height) {
return false;
}
function resolve_url_path(url) {
if (url.charAt(0) != "/" && url.indexOf("://") < 0) {
// adjust relative url path
url = krpano.parsepath("%CURRENTXML%/" + url);
}
return url;
}
function load_scripts(urls, callback) {
if (urls.length > 0) {
var url = resolve_url_path(urls.splice(0, 1)[0]);
var script = document.createElement("script");
script.src = url;
script.addEventListener("load", function () { load_scripts(urls, callback); });
script.addEventListener("error", function () { krpano.trace(3, "loading file '" + url + "' failed!"); });
document.getElementsByTagName("head")[0].appendChild(script);
}
else {
// done
callback();
}
}
// helper
var M_RAD = Math.PI / 180.0;
// ThreeJS/krpano objects
var renderer = null;
var scene = null;
var camera = null;
var stereocamera = null;
var camera_hittest_raycaster = null;
var krpano_panoview = null;
var krpano_panoview_euler = null;
var krpano_projection = new Float32Array(16); // krpano projection matrix
var krpano_depthbuffer_scale = 1.0001; // depthbuffer scaling (use ThreeJS defaults: znear=0.1, zfar=2000)
var krpano_depthbuffer_offset = -0.2;
function start() {
// create the ThreeJS WebGL renderer, but use the WebGL context from krpano
renderer = new THREE.WebGLRenderer({ canvas: krpano.webGL.canvas, context: krpano.webGL.context });
renderer.autoClear = false;
renderer.setPixelRatio(1); // krpano handles the pixel ratio scaling
// restore the krpano WebGL settings (for correct krpano rendering)
restore_krpano_WebGL_state();
// use the krpano onviewchanged event as render-frame callback (this event will be directly called after the krpano pano rendering)
krpano.set("events[__threejs__].keep", true);
krpano.set("events[__threejs__].onviewchange", adjust_krpano_rendering); // correct krpano view settings before the rendering
krpano.set("events[__threejs__].onviewchanged", render_frame);
// enable continuous rendering (that means render every frame, not just when the view has changed)
krpano.view.continuousupdates = true;
// register mouse and touch events
if (device.browser.events.mouse) {
krpano.control.layer.addEventListener("mousedown", handle_mouse_touch_events, true);
}
if (device.browser.events.touch) {
krpano.control.layer.addEventListener(device.browser.events.touchstart, handle_mouse_touch_events, true);
}
// basic ThreeJS objects
scene = new THREE.Scene();
camera = new THREE.Camera();
stereocamera = new THREE.Camera();
camera_hittest_raycaster = new THREE.Raycaster();
krpano_panoview_euler = new THREE.Euler();
console.log(camera);
// camera.position.z = 1;
// build the ThreeJS scene (start adding custom code there)
build_scene();
// restore the krpano WebGL settings (for correct krpano rendering)
restore_krpano_WebGL_state();
}
function restore_krpano_WebGL_state() {
var gl = krpano.webGL.context;
gl.disable(gl.DEPTH_TEST);
gl.cullFace(gl.FRONT);
gl.frontFace(gl.CCW);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.activeTexture(gl.TEXTURE0);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4);
// restore the current krpano WebGL program
krpano.webGL.restoreProgram();
//renderer.resetGLState();
}
function restore_ThreeJS_WebGL_state() {
var gl = krpano.webGL.context;
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
gl.clearDepth(1);
gl.clear(gl.DEPTH_BUFFER_BIT);
// renderer.resetGLState();
}
function krpano_projection_matrix(sw, sh, zoom, xoff, yoff) {
var m = krpano_projection;
var pr = device.pixelratio;
sw = pr / (sw * 0.5);
sh = pr / (sh * 0.5);
m[0] = zoom * sw; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = -zoom * sh; m[6] = 0; m[7] = 0;
m[8] = xoff; m[9] = -yoff * sh; m[10] = krpano_depthbuffer_scale; m[11] = 1;
m[12] = 0; m[13] = 0; m[14] = krpano_depthbuffer_offset; m[15] = 1;
}
function update_camera_matrix(camera) {
var m = krpano_projection;
camera.projectionMatrix.set(m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]);
}
function adjust_krpano_rendering() {
if (krpano.view.fisheye != 0.0) {
// disable the fisheye distortion, ThreeJS objects can't be rendered with it
krpano.view.fisheye = 0.0;
}
}
function render_frame() {
var gl = krpano.webGL.context;
var vr = krpano.webVR && krpano.webVR.enabled ? krpano.webVR : null;
var sw = gl.drawingBufferWidth;
var sh = gl.drawingBufferHeight;
// setup WebGL for ThreeJS
restore_ThreeJS_WebGL_state();
// set the camera/view rotation
krpano_panoview = krpano.view.getState(krpano_panoview); // the 'krpano_panoview' object will be created and cached inside getState()
krpano_panoview_euler.set(-krpano_panoview.v * M_RAD, (krpano_panoview.h - 90) * M_RAD, krpano_panoview.r * M_RAD, "YXZ");
camera.quaternion.setFromEuler(krpano_panoview_euler);
camera.updateMatrixWorld(true);
// set the camera/view projection
krpano_projection_matrix(sw, sh, krpano_panoview.z, 0, krpano_panoview.yf);
update_camera_matrix(camera);
// do scene updates
update_scene();
// render the scene
if (krpano.display.stereo == false) {
// normal rendering
renderer.setViewport(0, 0, sw, sh);
renderer.render(scene, camera);
}
else {
// stereo / VR rendering
sw *= 0.5; // use half screen width
var stereo_scale = 0.05;
var stereo_offset = Number(krpano.display.stereooverlap);
// use a different camera for stereo rendering to keep the normal one for hit-testing
stereocamera.quaternion.copy(camera.quaternion);
stereocamera.updateMatrixWorld(true);
// render left eye
var eye_offset = -0.03;
krpano_projection_matrix(sw, sh, krpano_panoview.z, stereo_offset, krpano_panoview.yf);
if (vr) {
eye_offset = vr.eyetranslt(1); // get the eye offset (from the WebVR API)
vr.prjmatrix(1, krpano_projection); // replace the projection matrix (with the one from WebVR)
krpano_projection[10] = krpano_depthbuffer_scale; // adjust the depthbuffer scaling
krpano_projection[14] = krpano_depthbuffer_offset;
}
// add the eye offset
krpano_projection[12] = krpano_projection[0] * -eye_offset * stereo_scale;
update_camera_matrix(stereocamera);
renderer.setViewport(0, 0, sw, sh);
renderer.render(scene, stereocamera);
// render right eye
eye_offset = +0.03;
krpano_projection[8] = -stereo_offset; // mod the projection matrix (only change the stereo offset)
if (vr) {
eye_offset = vr.eyetranslt(2); // get the eye offset (from the WebVR API)
vr.prjmatrix(2, krpano_projection); // replace the projection matrix (with the one from WebVR)
krpano_projection[10] = krpano_depthbuffer_scale; // adjust the depthbuffer scaling
krpano_projection[14] = krpano_depthbuffer_offset;
}
// add the eye offset
krpano_projection[12] = krpano_projection[0] * -eye_offset * stereo_scale;
update_camera_matrix(stereocamera);
renderer.setViewport(sw, 0, sw, sh);
renderer.render(scene, stereocamera);
}
// important - restore the krpano WebGL state for correct krpano rendering
restore_krpano_WebGL_state();
}
// -----------------------------------------------------------------------
// ThreeJS User Content - START HERE
var clock = null;
var animatedobjects = [];
var box = null;
// add a krpano hotspot like handling for the 3d objects
function assign_object_properties(obj, name, properties) {
// set defaults (krpano hotspot like properties)
// console.log(name);
if (properties === undefined) properties = {};
if (properties.name === undefined) properties.name = name;
if (properties.ath === undefined) properties.ath = 0;
if (properties.atv === undefined) properties.atv = 0;
if (properties.depth === undefined) properties.depth = 1000;
if (properties.scale === undefined) properties.scale = 1;
if (properties.rx === undefined) properties.rx = 0;
if (properties.ry === undefined) properties.ry = 0;
if (properties.rz === undefined) properties.rz = 0;
if (properties.rorder === undefined) properties.rorder = "YXZ";
if (properties.enabled === undefined) properties.enabled = true;
if (properties.capture === undefined) properties.capture = true;
if (properties.onover === undefined) properties.onover = null;
if (properties.onout === undefined) properties.onout = null;
if (properties.ondown === undefined) properties.ondown = null;
if (properties.onup === undefined) properties.onup = null;
if (properties.onclick === undefined) properties.onclick = null;
properties.pressed = false;
properties.hovering = false;
obj.properties = properties;
update_object_properties(obj);
}
function update_object_properties(obj) {
var p = obj.properties;
var px = p.depth * Math.cos(p.atv * M_RAD) * Math.cos((180 - p.ath) * M_RAD);
var py = p.depth * Math.sin(p.atv * M_RAD);
var pz = p.depth * Math.cos(p.atv * M_RAD) * Math.sin((180 - p.ath) * M_RAD);
obj.position.set(px, py, pz);
obj.rotation.set(p.rx * M_RAD, p.ry * M_RAD, p.rz * M_RAD, p.rorder);
obj.scale.set(p.scale, p.scale, p.scale);
obj.updateMatrix();
}
function load_object_obj(url, scene, properties, donecall = null) {
url = resolve_url_path(url);
const objPath = url;
const mtlPath = url.replace('.obj', '.mtl');
return new Promise((resolve, reject) => {
const mtlLoader = new THREE.MTLLoader();
mtlLoader.load(mtlPath, function (materials) {
materials.preload();
let loader = new THREE.OBJLoader();
loader.setMaterials(materials);
loader.load(objPath, function (object) {
object.children.forEach(element => {
const { geometry, material } = element
geometry.computeVertexNormals();
var obj = new THREE.Mesh(geometry, material);
obj.material.transparent = true
obj.material.side = THREE.DoubleSide
assign_object_properties(obj, element.name, properties);
scene.add(obj)
});
resolve(object);
}, null, function (error) {
console.error('Error loading object:', error);
reject(error);
});
})
})
}
function build_scene() {
clock = new THREE.Clock();
load_object_obj("Alfa_OBJ.obj", scene, {
ath: 0, atv: 0, depth: 0, scale: 2, rx: 180, ry: 0, rz: 0,
ondown: function (obj) { obj.properties.scale *= 1.2; update_object_properties(obj); }, onup: function (obj) { obj.properties.scale /= 1.2; update_object_properties(obj); }
}).then((obj) => {
console.log("obj", obj);
obj.children.forEach((elem) => {
elem.material.opacity = 0.5
elem.material.side = THREE.DoubleSide
elem.material.transparent = true
})
})
// // create a textured 3d box
// box = new THREE.Mesh(new THREE.BoxGeometry(500, 500, 500), new THREE.MeshBasicMaterial({ opacity: 0.1, side: THREE.BackSide, transparent: true }));
// assign_object_properties(box, "box", { ath: 160, atv: -3, depth: 2000, ondown: function (obj) { obj.properties.scale *= 1.2; }, onup: function (obj) { obj.properties.scale /= 1.2; } });
// scene.add(box);
// add scene lights
scene.add(new THREE.AmbientLight(0xFFFFFF));
var directionalLight = new THREE.DirectionalLight(0xFFFFFF);
directionalLight.position.x = 0.5;
directionalLight.position.y = -1;
directionalLight.position.z = 0;
directionalLight.position.normalize();
scene.add(directionalLight);
console.log("scenee", scene);
}
function do_object_hittest(mx, my) {
var mouse_x = (mx / krpano.area.pixelwidth) * 2.0 - 1.0;
var mouse_y = (my / krpano.area.pixelheight) * 2.0 - 1.0;
if (krpano.display.stereo) {
mouse_x += (mouse_x < 0.0 ? +1 : -1) * (1.0 - Number(krpano.display.stereooverlap)) * 0.5;
}
camera_hittest_raycaster.ray.direction.set(mouse_x, -mouse_y, 1.0).unproject(camera).normalize();
// scene.add(new THREE.ArrowHelper(camera_hittest_raycaster.ray.direction, camera_hittest_raycaster.ray.origin, 300, 0xff0000));
var intersects = camera_hittest_raycaster.intersectObjects(scene.children, true);
var i;
var obj;
for (i = 0; i < intersects.length; i++) {
obj = intersects[i].object;
if (obj && obj.properties && obj.properties.enabled) {
return obj;
}
}
return null;
}
var handle_mouse_hitobject = null;
function handle_mouse_touch_events(event) {
var type = "";
if (event.type == "mousedown") {
type = "ondown";
// handle_mouse_click(event)
krpano.control.layer.addEventListener("mouseup", handle_mouse_touch_events, true);
}
else if (event.type == "mouseup") {
type = "onup";
krpano.control.layer.removeEventListener("mouseup", handle_mouse_touch_events, true);
}
else if (event.type == device.browser.events.touchstart) {
type = "ondown";
krpano.control.layer.addEventListener(device.browser.events.touchend, handle_mouse_touch_events, true);
}
else if (event.type == device.browser.events.touchend) {
type = "onup";
krpano.control.layer.removeEventListener(device.browser.events.touchend, handle_mouse_touch_events, true);
}
// get mouse / touch pos
var ms = krpano.control.getMousePos(event.changedTouches ? event.changedTouches[0] : event);
ms.x /= krpano.stagescale;
ms.y /= krpano.stagescale;
// is there a object as that pos?
var hitobj = do_object_hittest(ms.x, ms.y);
if (type == "ondown") {
if (hitobj) {
handle_mouse_hitobject = hitobj;
hitobj.properties.pressed = true;
if (hitobj.properties.ondown) {
hitobj.properties.ondown(hitobj);
}
if (hitobj.properties.capture) {
krpano.mouse.down = true;
//event.stopPropagation();
console.log(hitobj)
}
event.preventDefault();
}
}
else if (type == "onup") {
if (handle_mouse_hitobject && handle_mouse_hitobject.properties.enabled) {
if (handle_mouse_hitobject.properties.pressed) {
handle_mouse_hitobject.properties.pressed = false;
if (handle_mouse_hitobject.properties.onup) {
handle_mouse_hitobject.properties.onup(handle_mouse_hitobject);
}
}
if (handle_mouse_hitobject.properties.onclick) {
if (hitobj == handle_mouse_hitobject) {
handle_mouse_hitobject.properties.onclick(handle_mouse_hitobject);
}
}
}
krpano.mouse.down = false;
}
}
function handle_mouse_click(event) {
// Get mouse position
var ms = krpano.control.getMousePos(event);
var mouse_x = (ms.x / krpano.stagescale) * 2.0 - 1.0;
var mouse_y = (ms.y / krpano.stagescale) * 2.0 - 1.0;
// Perform hit testing to check if an object was clicked
var hitobj = do_object_hittest(mouse_x, mouse_y);
if (hitobj) {
console.log("Clicked object:", hitobj);
if (hitobj.properties.onclick) {
hitobj.properties.onclick(hitobj);
}
}
}
function handle_mouse_hovering() {
// check mouse over state
if (krpano.mouse.down == false) // currently not dragging?
{
var hitobj = do_object_hittest(krpano.mouse.x, krpano.mouse.y);
// console.log("hitobjhitobj", hitobj);
if (hitobj != handle_mouse_hitobject) {
if (handle_mouse_hitobject) {
handle_mouse_hitobject.properties.hovering = false;
if (handle_mouse_hitobject.properties.onout) handle_mouse_hitobject.properties.onout(handle_mouse_hitobject);
}
if (hitobj) {
hitobj.properties.hovering = true;
if (hitobj.properties.onover) hitobj.properties.onover(hitobj);
}
handle_mouse_hitobject = hitobj;
}
if (handle_mouse_hitobject)// || (krpano.display.stereo == false && krpano.display.hotspotrenderer != "webgl"))
{
krpano.control.layer.style.cursor = krpano.cursors.hit;
}
else {
krpano.cursors.update();
}
}
}
function update_scene() {
// animate objects
var delta = clock.getDelta();
if (box) {
box.properties.rx += 50 * delta;
box.properties.ry += 10 * delta;
update_object_properties(box);
}
for (var i = 0; i < animatedobjects.length; i++) {
animatedobjects[i].updateAnimation(1000 * delta);
}
handle_mouse_hovering();
}
}