diff --git a/examples/surface-viewer-demo.html b/examples/surface-viewer-demo.html index 6d392dd2..63046542 100644 --- a/examples/surface-viewer-demo.html +++ b/examples/surface-viewer-demo.html @@ -191,6 +191,11 @@

Views:

Display axes

+

+

+ +
+

Reset View diff --git a/examples/surface-viewer-demo.js b/examples/surface-viewer-demo.js index f5fa7368..1ee50665 100644 --- a/examples/surface-viewer-demo.js +++ b/examples/surface-viewer-demo.js @@ -494,6 +494,11 @@ $(function() { }); + // Origin position + $("#model_centric").change(function() { + viewer.modelCentric($(this).is(":checked")); + }); + // Color map URLs are read from the config file and added to the // color map select box. var color_map_select = $('').change(function() { @@ -511,6 +516,7 @@ $(function() { viewer.clearScreen(); current_request = 0; current_request_name = ""; + document.getElementById("model_centric").checked = false; loading_div.hide(); }); @@ -519,8 +525,8 @@ $(function() { if (viewer.model.children.length === 0) return; var annotation_display = $("#annotation-display"); - var media = $("#annotation-media"); - var pick_info = viewer.pick(); + var media = $("#annotation-media"); + var pick_info = viewer.pick(); var model_data, intensity_data; var annotation_info; var value, label, text; @@ -532,8 +538,8 @@ $(function() { $("#pick-index").html(pick_info.index); $("#annotation-wrapper").show(); - picked_object = pick_info.object; - model_data = viewer.model_data.get(picked_object.userData.model_name); + picked_object = pick_info.object; + model_data = viewer.model_data.get(picked_object.userData.model_name); intensity_data = model_data.intensity_data[0]; if (intensity_data) { @@ -634,9 +640,9 @@ $(function() { }); $("#annotation-save").click(function() { - var vertex_num = parseInt($("#pick-index").html(), 10); + var vertex_num = parseInt($("#pick-index").html(), 10); var annotation_display = $("#annotation-display"); - var media = $("#annotation-media"); + var media = $("#annotation-media"); var annotation, annotation_data; var vertex; @@ -658,15 +664,15 @@ $(function() { vertex = viewer.getVertex(vertex_num); annotation_data.image = $("#annotation-image").val(); - annotation_data.url = $("#annotation-url").val(); - annotation_data.text = $("#annotation-text").val(); + annotation_data.url = $("#annotation-url").val(); + annotation_data.text = $("#annotation-text").val(); media.html(""); if (annotation_data.image) { - var image = new Image(); + var image = new Image(); image.width = 200; - image.src = annotation_data.image; + image.src = annotation_data.image; annotation_display.show(); media.append(image); } @@ -914,7 +920,14 @@ $(function() { showLoading(); viewer.loadModelFromFile(document.getElementById("objfile"), { format: format, - complete: hideLoading + complete: function() { + document.getElementById("model_centric").checked = true; + viewer.modelCentric(true); + $("#vertex-data-wrapper").show(); + $("#pick-value-wrapper").show(); + $("#pick-label-wrapper").show(); + hideLoading(); + } }); return false; diff --git a/src/brainbrowser/surface-viewer/modules/loading.js b/src/brainbrowser/surface-viewer/modules/loading.js index 733edef3..084d9c14 100644 --- a/src/brainbrowser/surface-viewer/modules/loading.js +++ b/src/brainbrowser/surface-viewer/modules/loading.js @@ -164,7 +164,7 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { * function. The function will receive the model description and the model name * as arguments. * ```js - * viewer.model_data.forEach(function(mode_data, model_name) { + * viewer.model_data.forEach(function(model_data, model_name) { * console.log(model_name, model_data.vertices.length); * }); * ``` @@ -363,9 +363,9 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { //////////////////////////////////// function loadModel(data, filename, options) { - options = options || {}; - var type = options.format || "mniobj"; - var parse_options = options.parse || {}; + options = options || {}; + var type = options.format || "mniobj"; + var parse_options = options.parse || {}; // Parse model info based on the given file type. parseModel(data, type, parse_options, function(model_data) { @@ -376,12 +376,12 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { } function loadIntensityData(text, filename, options) { - options = options || {}; - var name = options.name || filename; - var type = options.format || "text"; - var blend = options.blend; - var model_name = options.model_name; - var model_data = viewer.model_data.get(model_name); + options = options || {}; + var name = options.name || filename; + var type = options.format || "text"; + var blend = options.blend; + var model_name = options.model_name; + var model_data = viewer.model_data.get(model_name); var intensity_data = model_data.intensity_data[0]; var old_range = {}; @@ -564,18 +564,18 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { /////////////////////////////////////////// // Creates three.js objects based on the - // description in **model_data** and + // description in **model_data** and // displays in on the viewer. function displayModel(model_data, filename, options) { - options = options || {}; + options = options || {}; var complete = options.complete; var new_shapes = createModel(model_data, filename, options); viewer.triggerEvent("displaymodel", { - model: viewer.model, + model: viewer.model, model_data: model_data, - new_shapes: new_shapes + new_shapes: new_shapes, }); if (complete) complete(); @@ -586,13 +586,13 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { // object, though they may share attributes and // buffers. function createModel(model_data, filename, options){ - var model = viewer.model; - var shapes = model_data.shapes; - var is_line = model_data.type === "line"; - var render_depth = options.render_depth; - var pick_ignore = options.pick_ignore; - var recenter = options.recenter || model_data.split; - var new_shapes = []; + var model = viewer.model; + var shapes = model_data.shapes; + var is_line = model_data.type === "line"; + var render_depth = options.render_depth; + var pick_ignore = options.pick_ignore; + var recenter = options.recenter || model_data.split; + var new_shapes = []; var shape, shape_data; var i, count; var object_description = {is_line: is_line}; @@ -628,9 +628,9 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { object_description = { position: position_buffer, - normal: normal_buffer, - color: color_buffer, - index: new THREE.BufferAttribute(new Uint32Array(shape_data.indices), 1), + normal: normal_buffer, + color: color_buffer, + index: new THREE.BufferAttribute(new Uint32Array(shape_data.indices), 1), }; } else { @@ -649,26 +649,26 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { object_description = { position: position_buffer, - normal: normal_buffer, - color: color_buffer + normal: normal_buffer, + color: color_buffer }; } - object_description.is_line = is_line; + object_description.is_line = is_line; object_description.centroid = shape_data.centroid; object_description.recenter = recenter; - shape = createShape(object_description); + shape = createShape(object_description); shape.name = shape_data.name || filename + "_" + (i + 1); shape.userData.model_name = model_data.name; shape.userData.original_data = { vertices: model_data.vertices, - indices: shape_data.indices, - normals: model_data.normals, - colors: model_data.colors + indices: shape_data.indices, + normals: model_data.normals, + colors: model_data.colors, }; shape.userData.pick_ignore = pick_ignore; @@ -694,14 +694,14 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { // a shape from the model data 'shapes' // array. function createShape(object_description) { - var position = object_description.position; + var position = object_description.position; var position_array = position.array; - var normal = object_description.normal; - var color = object_description.color; - var index = object_description.index; - var centroid = object_description.centroid; - var is_line = object_description.is_line; - var recenter = object_description.recenter; + var normal = object_description.normal; + var color = object_description.color; + var index = object_description.index; + var centroid = object_description.centroid; + var is_line = object_description.is_line; + var recenter = object_description.recenter; var geometry = new THREE.BufferGeometry(); var index_array, tmp_position_array, position_index; @@ -748,10 +748,10 @@ BrainBrowser.SurfaceViewer.modules.loading = function(viewer) { if (is_line) { material = new THREE.LineBasicMaterial({vertexColors: THREE.VertexColors}); - shape = new THREE.Line(geometry, material, THREE.LinePieces); + shape = new THREE.Line(geometry, material, THREE.LinePieces); } else { material = new THREE.MeshPhongMaterial({color: 0xFFFFFF, ambient: 0xFFFFFF, specular: 0x101010, shininess: 150, vertexColors: THREE.VertexColors}); - shape = new THREE.Mesh(geometry, material); + shape = new THREE.Mesh(geometry, material); shape.userData.has_wireframe = true; } diff --git a/src/brainbrowser/surface-viewer/modules/rendering.js b/src/brainbrowser/surface-viewer/modules/rendering.js index 4b31927d..e9959969 100644 --- a/src/brainbrowser/surface-viewer/modules/rendering.js +++ b/src/brainbrowser/surface-viewer/modules/rendering.js @@ -199,6 +199,7 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { camera.position.set(0, 0, default_camera_distance); light.position.set(0, 0, default_camera_distance); + var offset = model.userData.model_center_offset || new THREE.Vector3(0,0,0); model.children.forEach(function(shape) { var centroid = shape.userData.centroid; var recentered = shape.userData.recentered; @@ -209,12 +210,12 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { if (shape.userData.original_data) { if (centroid && recentered) { shape.position.set( - centroid.x, - centroid.y, - centroid.z + centroid.x + offset.x, + centroid.y + offset.y, + centroid.z + offset.z ); } else { - shape.position.set(0, 0, 0); + shape.position.set(offset.x, offset.y, offset.z); } shape.rotation.set(0, 0, 0); shape.material.opacity = 1; @@ -269,10 +270,17 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { var geometry = new THREE.SphereGeometry(radius); var material = new THREE.MeshBasicMaterial({color: color}); - var sphere = new THREE.Mesh(geometry, material); + var sphere = new THREE.Mesh(geometry, material); sphere.position.set(x, y, z); if (viewer.model) { + var offset = viewer.model.userData.model_center_offset; + var is_centric = viewer.model.userData.model_centric; + if (offset !== undefined && is_centric === true) { + sphere.translateX(offset.x); + sphere.translateY(offset.y); + sphere.translateZ(offset.z); + } viewer.model.add(sphere); } else { scene.add(sphere); @@ -488,8 +496,8 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { y = (-y / viewer.dom_element.offsetHeight) * 2 + 1; var model = viewer.model; - var raycaster = new THREE.Raycaster(); - var vector = new THREE.Vector3(x, y, camera.near); + var raycaster = new THREE.Raycaster(); + var vector = new THREE.Vector3(x, y, camera.near); var intersection = null; var intersects, vertex_data; var intersect_object, intersect_point, intersect_indices, intersect_face; @@ -623,6 +631,111 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { return vertex_data; }; + + /** + * @doc function + * @name viewer.rendering:modelCentric + * @param {boolean} if true, recenter all userData shape on origin. Otherwise + * return to the original userData. + * + * @description + * Use to recenter data when userData input is shifted in space. + * + * + * ```js + * viewer.modelCentric(true); + * ``` + */ + viewer.modelCentric = function(model_centric) { + if (model_centric === undefined) { + model_centric = false; + } + + var model = viewer.model; + viewer.findUserDataCentroid(model); + + if (model_centric === model.userData.model_centric) { + return; + } + + // Caculate the offset + var offset_centroid = new THREE.Vector3(); + offset_centroid.copy(model.userData.model_center_offset); + if (model_centric === false) { + offset_centroid.negate(); + } + + model.children.forEach(function(children) { + // Return if children is not given by the user + if (Object.keys(children.userData).length === 0 && children.userData.constructor === Object) { + return; + } + children.translateX(offset_centroid.x); + children.translateY(offset_centroid.y); + children.translateZ(offset_centroid.z); + }); + model.userData.model_centric = model_centric; + + viewer.updated = true; + }; + + /** + * @doc function + * @name viewer.rendering:findUserDataCentroid + * @param {object} a model. + * + * @description + * Find centroid of the model (only take in account userData). + * + * @returns {object} The initial information with additionnal model_center_offset argument. + * + * ```js + * viewer.findUserDataCentroid(true); + * ``` + */ + viewer.findUserDataCentroid = function(model) { + // Calculate only if needed + if (model.userData.model_center_offset !== undefined) { + return; + } + + // Calculate bounding box for all children given by the user + // ignore other children + var min_x, max_x, min_y, max_y, min_z, max_z; + min_x = min_y = min_z = Number.POSITIVE_INFINITY; + max_x = max_y = max_z = Number.NEGATIVE_INFINITY; + + model.children.forEach(function(children){ + var model_name = children.userData.model_name; + var model_data = viewer.model_data.get(model_name); + + var children_name = children.name; + model_data.shapes.forEach(function(shape){ + if (shape.name !== children_name) { + return; + } + var bounding_box = shape.bounding_box; + + // min + min_x = Math.min(min_x, bounding_box.min_x); + min_y = Math.min(min_y, bounding_box.min_y); + min_z = Math.min(min_z, bounding_box.min_z); + // max + max_x = Math.max(max_x, bounding_box.max_x); + max_y = Math.max(max_y, bounding_box.max_y); + max_z = Math.max(max_z, bounding_box.max_z); + }); + + // centroid of all the model + var centroid = new THREE.Vector3(); + centroid.x = min_x + (max_x - min_x) / 2; + centroid.y = min_y + (max_y - min_y) / 2; + centroid.z = min_z + (max_z - min_z) / 2; + + model.userData.model_center_offset = new THREE.Vector3(-centroid.x, -centroid.y, -centroid.z); + }); + }; + //////////////////////////////////// // PRIVATE FUNCTIONS //////////////////////////////////// @@ -633,7 +746,7 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { var delta; var rotation; var position = camera.position; - var new_z = default_camera_distance / viewer.zoom; + var new_z = default_camera_distance / viewer.zoom; window.requestAnimationFrame(renderFrame); @@ -689,8 +802,8 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { function drag(pointer, multiplier) { var inverse = new THREE.Matrix4(); - var x = pointer.x; - var y = pointer.y; + var x = pointer.x; + var y = pointer.y; var dx, dy; @@ -700,8 +813,7 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { if (movement === "rotate") { - // Want to always be rotating around - // world axes. + // Want to always be rotating around world axes. inverse.getInverse(model.matrix); var axis = new THREE.Vector3(1, 0, 0).applyMatrix4(inverse).normalize(); model.rotateOnAxis(axis, dy / 150); @@ -710,13 +822,13 @@ BrainBrowser.SurfaceViewer.modules.rendering = function(viewer) { axis = new THREE.Vector3(0, 1, 0).applyMatrix4(inverse).normalize(); model.rotateOnAxis(axis, dx / 150); } else { - multiplier = multiplier || 1.0; + multiplier = multiplier || 1.0; multiplier *= camera.position.z / default_camera_distance; camera.position.x -= dx * multiplier * 0.25; - light.position.x -= dx * multiplier * 0.25; + light.position.x -= dx * multiplier * 0.25; camera.position.y += dy * multiplier * 0.25; - light.position.y += dy * multiplier * 0.25; + light.position.y += dy * multiplier * 0.25; } }