diff --git a/Source/Scene/Axis.js b/Source/Scene/Axis.js new file mode 100644 index 000000000000..e98bf2f81466 --- /dev/null +++ b/Source/Scene/Axis.js @@ -0,0 +1,111 @@ +/*global define*/ +define([ + '../Core/Check', + '../Core/freezeObject', + '../Core/Math', + '../Core/Matrix3', + '../Core/Matrix4' +], function( + Check, + freezeObject, + CesiumMath, + Matrix3, + Matrix4) { + 'use strict'; + + /** + * An enum describing the x, y, and z axes and helper conversion functions. + * + * @exports Axis + * @private + */ + var Axis = { + /** + * Denotes the x-axis. + * + * @type {Number} + * @constant + */ + X : 0, + + /** + * Denotes the y-axis. + * + * @type {Number} + * @constant + */ + Y : 1, + + /** + * Denotes the z-axis. + * + * @type {Number} + * @constant + */ + Z : 2, + + /** + * Matrix used to convert from y-up to z-up + * + * @type {Matrix4} + * @constant + */ + Y_UP_TO_Z_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationX(CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from z-up to y-up + * + * @type {Matrix4} + * @constant + */ + Z_UP_TO_Y_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationX(-CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from x-up to z-up + * + * @type {Matrix4} + * @constant + */ + X_UP_TO_Z_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationY(-CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from z-up to x-up + * + * @type {Matrix4} + * @constant + */ + Z_UP_TO_X_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationY(CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from x-up to y-up + * + * @type {Matrix4} + * @constant + */ + X_UP_TO_Y_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from y-up to x-up + * + * @type {Matrix4} + * @constant + */ + Y_UP_TO_X_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(-CesiumMath.PI_OVER_TWO)), + + /** + * Gets the axis by name + * + * @param {String} name The name of the axis. + * @returns {Number} The axis enum. + */ + fromName : function(name) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('name', name); + //>>includeEnd('debug'); + + return Axis[name]; + } + }; + + return freezeObject(Axis); +}); diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 7f1443d9e048..98f23b653444 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -52,6 +52,7 @@ define([ '../ThirdParty/gltfDefaults', '../ThirdParty/Uri', '../ThirdParty/when', + './Axis', './BlendingState', './ColorBlendMode', './getAttributeOrUniformBySemantic', @@ -118,6 +119,7 @@ define([ gltfDefaults, Uri, when, + Axis, BlendingState, ColorBlendMode, getAttributeOrUniformBySemantic, @@ -139,7 +141,6 @@ define([ return {}; } - var yUpToZUp = Matrix4.fromRotationTranslation(Matrix3.fromRotationX(CesiumMath.PI_OVER_TWO)); var boundingSphereCartesian3Scratch = new Cartesian3(); var ModelState = { @@ -623,6 +624,7 @@ define([ this._pickFragmentShaderLoaded = options.pickFragmentShaderLoaded; this._pickUniformMapLoaded = options.pickUniformMapLoaded; this._ignoreCommands = defaultValue(options.ignoreCommands, false); + this._upAxis = defaultValue(options.upAxis, Axis.Y); /** * @private @@ -956,6 +958,24 @@ define([ //>>includeEnd('debug'); this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); } + }, + + /** + * Gets the model's up-axis. + * By default models are y-up according to the glTF spec, however geo-referenced models will typically be z-up. + * + * @memberof Model.prototype + * + * @type {Number} + * @default Axis.Y + * @readonly + * + * @private + */ + upAxis : { + get : function() { + return this._upAxis; + } } }); @@ -1233,7 +1253,7 @@ define([ }; } - function computeBoundingSphere(gltf) { + function computeBoundingSphere(model, gltf) { var gltfNodes = gltf.nodes; var gltfMeshes = gltf.meshes; var rootNodes = gltf.scenes[gltf.scene].nodes; @@ -1289,7 +1309,12 @@ define([ } var boundingSphere = BoundingSphere.fromCornerPoints(min, max); - return BoundingSphere.transformWithoutScale(boundingSphere, yUpToZUp, boundingSphere); + if (model._upAxis === Axis.Y) { + BoundingSphere.transformWithoutScale(boundingSphere, Axis.Y_UP_TO_Z_UP, boundingSphere); + } else if (model._upAxis === Axis.X) { + BoundingSphere.transformWithoutScale(boundingSphere, Axis.X_UP_TO_Z_UP, boundingSphere); + } + return boundingSphere; } /////////////////////////////////////////////////////////////////////////// @@ -4192,7 +4217,7 @@ define([ this._state = ModelState.LOADING; - this._boundingSphere = computeBoundingSphere(this.gltf); + this._boundingSphere = computeBoundingSphere(this, this.gltf); this._initialRadius = this._boundingSphere.radius; checkSupportedExtensions(this); @@ -4315,7 +4340,11 @@ define([ var scale = getScale(this, frameState); var computedModelMatrix = this._computedModelMatrix; Matrix4.multiplyByUniformScale(modelMatrix, scale, computedModelMatrix); - Matrix4.multiplyTransformation(computedModelMatrix, yUpToZUp, computedModelMatrix); + if (this._upAxis === Axis.Y) { + Matrix4.multiplyTransformation(computedModelMatrix, Axis.Y_UP_TO_Z_UP, computedModelMatrix); + } else if (this._upAxis === Axis.X) { + Matrix4.multiplyTransformation(computedModelMatrix, Axis.X_UP_TO_Z_UP, computedModelMatrix); + } } // Update modelMatrix throughout the graph as needed diff --git a/Specs/Data/Models/Box-ECEF/ecef.gltf b/Specs/Data/Models/Box-ECEF/ecef.gltf new file mode 100644 index 000000000000..eae2d497647a --- /dev/null +++ b/Specs/Data/Models/Box-ECEF/ecef.gltf @@ -0,0 +1,312 @@ +{ + "accessors": { + "accessor_position": { + "bufferView": "bufferView_0", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + 1215011.25, + -4736310, + 4081601.25 + ], + "max": [ + 1215012.625, + -4736308.5, + 4081602.75 + ] + }, + "accessor_normal": { + "bufferView": "bufferView_0", + "byteOffset": 288, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -0.968635618686676, + -0.7415555715560913, + -0.7655670642852783 + ], + "max": [ + 0.968635618686676, + 0.7415555715560913, + 0.7655670642852783 + ] + }, + "accessor_uv": { + "bufferView": "bufferView_0", + "byteOffset": 576, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "type": "VEC2", + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ] + }, + "accessor_index_0": { + "bufferView": "bufferView_1", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5123, + "count": 36, + "type": "SCALAR", + "min": [ + 0 + ], + "max": [ + 23 + ] + } + }, + "asset": { + "generator": "3d-tiles-generator", + "version": "1.1", + "premultipliedAlpha": true, + "profile": { + "api": "WebGL", + "version": "1.0.3" + } + }, + "buffers": { + "bufferView_0_buffer": { + "type": "arraybuffer", + "byteLength": 840, + "uri": "data:application/octet-stream;base64,HVGUSWyKkMoIH3lKJVGUSWyKkMoIH3lKI1GUSWuKkMoLH3lKHFGUSWuKkMoLH3lKIlGUSWmKkMoIH3lKI1GUSWqKkMoFH3lKG1GUSWuKkMoFH3lKGlGUSWqKkMoIH3lKI1GUSWqKkMoFH3lKIlGUSWmKkMoIH3lKI1GUSWuKkMoLH3lKJVGUSWyKkMoIH3lKHFGUSWuKkMoLH3lKGlGUSWqKkMoIH3lKG1GUSWuKkMoFH3lKHVGUSWyKkMoIH3lKI1GUSWuKkMoLH3lKIlGUSWmKkMoIH3lKGlGUSWqKkMoIH3lKHFGUSWuKkMoLH3lKG1GUSWuKkMoFH3lKI1GUSWqKkMoFH3lKJVGUSWyKkMoIH3lKHVGUSWyKkMoIH3lKP8xCPpbWPb/8siQ/P8xCPpbWPb/8siQ/P8xCPpbWPb/8siQ/P8xCPpbWPb/8siQ/P8xCvpbWPT/8siS/P8xCvpbWPT/8siS/P8xCvpbWPT/8siS/P8xCvpbWPT/8siS/gfh3P/dyfj4AAAAAgfh3P/dyfj4AAAAAgfh3P/dyfj4AAAAAgfh3P/dyfj4AAAAAgfh3v/dyfr4AAAAAgfh3v/dyfr4AAAAAgfh3v/dyfr4AAAAAgfh3v/dyfr4AAAAAjLMjvpGIHz80/EM/jLMjvpGIHz80/EM/jLMjvpGIHz80/EM/jLMjvpGIHz80/EM/jLMjPpGIH780/EO/jLMjPpGIH780/EO/jLMjPpGIH780/EO/jLMjPpGIH780/EO/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AAAAAAAAgD8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAIA/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAIA/AAABAAIAAAACAAMABAAFAAYABwAEAAYACAAJAAoACAAKAAsADAANAA4ADwAMAA4AEAARABIAEwAQABIAFAAVABYAFAAWABcA" + } + }, + "bufferViews": { + "bufferView_0": { + "buffer": "bufferView_0_buffer", + "byteLength": 768, + "byteOffset": 0, + "target": 34962 + }, + "bufferView_1": { + "buffer": "bufferView_0_buffer", + "byteLength": 72, + "byteOffset": 768, + "target": 34963 + } + }, + "extensionsRequired": [ + "KHR_materials_common" + ], + "images": {}, + "materials": { + "material_0": { + "extensions": {}, + "technique": "technique0", + "values": { + "ambient": [ + 0.1, + 0.1, + 0.1, + 1 + ], + "diffuse": [ + 1, + 1, + 1, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0, + 0, + 0, + 1 + ], + "shininess": 0, + "transparency": 1 + } + } + }, + "meshes": { + "mesh": { + "primitives": [ + { + "attributes": { + "POSITION": "accessor_position", + "NORMAL": "accessor_normal", + "TEXCOORD_0": "accessor_uv" + }, + "indices": "accessor_index_0", + "material": "material_0", + "mode": 4 + } + ] + } + }, + "nodes": { + "rootNode": { + "children": [], + "meshes": [ + "mesh" + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "node_transform": { + "children": [ + "rootNode" + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 0, + -1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ], + "name": "Y_UP_Transform" + } + }, + "samplers": {}, + "scene": "scene", + "scenes": { + "scene": { + "nodes": [ + "node_transform" + ] + } + }, + "textures": {}, + "animations": {}, + "cameras": {}, + "programs": { + "program0": { + "attributes": [ + "a_position", + "a_normal", + "a_texcoord_0" + ], + "fragmentShader": "fragmentShader0", + "vertexShader": "vertexShader0" + } + }, + "shaders": { + "vertexShader0": { + "type": 35633, + "uri": "data:text/plain;base64,YXR0cmlidXRlIHZlYzIgYV90ZXhjb29yZF8wOwpwcmVjaXNpb24gaGlnaHAgZmxvYXQ7CnVuaWZvcm0gbWF0NCB1X21vZGVsVmlld01hdHJpeDsKdW5pZm9ybSBtYXQ0IHVfcHJvamVjdGlvbk1hdHJpeDsKdW5pZm9ybSBtYXQzIHVfbm9ybWFsTWF0cml4OwphdHRyaWJ1dGUgdmVjMyBhX3Bvc2l0aW9uOwp2YXJ5aW5nIHZlYzMgdl9wb3NpdGlvbkVDOwphdHRyaWJ1dGUgdmVjMyBhX25vcm1hbDsKdmFyeWluZyB2ZWMzIHZfbm9ybWFsOwp2b2lkIG1haW4odm9pZCkgewogIHZlYzQgcG9zID0gdV9tb2RlbFZpZXdNYXRyaXggKiB2ZWM0KGFfcG9zaXRpb24sMS4wKTsKICB2X3Bvc2l0aW9uRUMgPSBwb3MueHl6OwogIGdsX1Bvc2l0aW9uID0gdV9wcm9qZWN0aW9uTWF0cml4ICogcG9zOwogIHZfbm9ybWFsID0gdV9ub3JtYWxNYXRyaXggKiBhX25vcm1hbDsKfQo=" + }, + "fragmentShader0": { + "type": 35632, + "uri": "data:text/plain;base64,cHJlY2lzaW9uIGhpZ2hwIGZsb2F0Owp1bmlmb3JtIHZlYzQgdV9hbWJpZW50Owp1bmlmb3JtIHZlYzQgdV9kaWZmdXNlOwp1bmlmb3JtIHZlYzQgdV9lbWlzc2lvbjsKdW5pZm9ybSB2ZWM0IHVfc3BlY3VsYXI7CnVuaWZvcm0gZmxvYXQgdV9zaGluaW5lc3M7CnVuaWZvcm0gZmxvYXQgdV90cmFuc3BhcmVuY3k7CnZhcnlpbmcgdmVjMyB2X3Bvc2l0aW9uRUM7CnZhcnlpbmcgdmVjMyB2X25vcm1hbDsKdm9pZCBtYWluKHZvaWQpIHsKICB2ZWMzIG5vcm1hbCA9IG5vcm1hbGl6ZSh2X25vcm1hbCk7CiAgdmVjNCBkaWZmdXNlID0gdV9kaWZmdXNlOwogIHZlYzMgZGlmZnVzZUxpZ2h0ID0gdmVjMygwLjAsIDAuMCwgMC4wKTsKICB2ZWMzIGVtaXNzaW9uID0gdV9lbWlzc2lvbi5yZ2I7CiAgdmVjMyBhbWJpZW50ID0gdV9hbWJpZW50LnJnYjsKICB2ZWMzIHZpZXdEaXIgPSAtbm9ybWFsaXplKHZfcG9zaXRpb25FQyk7CiAgdmVjMyBhbWJpZW50TGlnaHQgPSB2ZWMzKDAuMCwgMC4wLCAwLjApOwogIGFtYmllbnRMaWdodCArPSB2ZWMzKDAuMiwgMC4yLCAwLjIpOwogIHZlYzMgbCA9IG5vcm1hbGl6ZShjem1fc3VuRGlyZWN0aW9uRUMpOwogIGRpZmZ1c2VMaWdodCArPSB2ZWMzKDEuMCwgMS4wLCAxLjApICogbWF4KGRvdChub3JtYWwsbCksIDAuKTsKICB2ZWMzIGNvbG9yID0gdmVjMygwLjAsIDAuMCwgMC4wKTsKICBjb2xvciArPSBkaWZmdXNlLnJnYiAqIGRpZmZ1c2VMaWdodDsKICBjb2xvciArPSBlbWlzc2lvbjsKICBjb2xvciArPSBhbWJpZW50ICogYW1iaWVudExpZ2h0OwogIGdsX0ZyYWdDb2xvciA9IHZlYzQoY29sb3IgKiBkaWZmdXNlLmEsIGRpZmZ1c2UuYSAqIHVfdHJhbnNwYXJlbmN5KTsKfQo=" + } + }, + "skins": {}, + "techniques": { + "technique0": { + "attributes": { + "a_position": "position", + "a_normal": "normal", + "a_texcoord_0": "texcoord_0" + }, + "parameters": { + "modelViewMatrix": { + "semantic": "MODELVIEW", + "type": 35676 + }, + "projectionMatrix": { + "semantic": "PROJECTION", + "type": 35676 + }, + "normalMatrix": { + "semantic": "MODELVIEWINVERSETRANSPOSE", + "type": 35675 + }, + "ambient": { + "type": 35666 + }, + "diffuse": { + "type": 35666, + "semantic": "_3DTILESDIFFUSE" + }, + "emission": { + "type": 35666 + }, + "specular": { + "type": 35666 + }, + "shininess": { + "type": 5126 + }, + "transparency": { + "type": 5126 + }, + "position": { + "semantic": "POSITION", + "type": 35665 + }, + "normal": { + "semantic": "NORMAL", + "type": 35665 + }, + "texcoord_0": { + "semantic": "TEXCOORD_0", + "type": 5126 + } + }, + "program": "program0", + "states": { + "enable": [ + 2884, + 2929 + ] + }, + "uniforms": { + "u_modelViewMatrix": "modelViewMatrix", + "u_projectionMatrix": "projectionMatrix", + "u_normalMatrix": "normalMatrix", + "u_ambient": "ambient", + "u_diffuse": "diffuse", + "u_emission": "emission", + "u_specular": "specular", + "u_shininess": "shininess", + "u_transparency": "transparency" + } + } + }, + "extensions": {}, + "extensionsUsed": [] +} diff --git a/Specs/Data/Models/Boxes-ECEF/ecef.glb b/Specs/Data/Models/Boxes-ECEF/ecef.glb deleted file mode 100644 index 5ce9b416c51e..000000000000 Binary files a/Specs/Data/Models/Boxes-ECEF/ecef.glb and /dev/null differ diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index c324aedcaf57..271643969cde 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -28,6 +28,7 @@ defineSuite([ 'Renderer/Pass', 'Renderer/RenderState', 'Renderer/ShaderSource', + 'Scene/Axis', 'Scene/ColorBlendMode', 'Scene/HeightReference', 'Scene/ModelAnimationLoop', @@ -63,6 +64,7 @@ defineSuite([ Pass, RenderState, ShaderSource, + Axis, ColorBlendMode, HeightReference, ModelAnimationLoop, @@ -85,7 +87,8 @@ defineSuite([ var texturedBoxCustomUrl = './Data/Models/Box-Textured-Custom/CesiumTexturedBoxTest.gltf'; var texturedBoxKhrBinaryUrl = './Data/Models/Box-Textured-Binary/CesiumTexturedBoxTest.glb'; var boxRtcUrl = './Data/Models/Box-RTC/Box.gltf'; - var boxesEcefUrl = './Data/Models/Boxes-ECEF/ecef.glb'; + var boxEcefUrl = './Data/Models/Box-ECEF/ecef.gltf'; + var cesiumAirUrl = './Data/Models/CesiumAir/Cesium_Air.gltf'; var cesiumAir_0_8Url = './Data/Models/CesiumAir/Cesium_Air_0_8.gltf'; var animBoxesUrl = './Data/Models/anim-test-1-boxes/anim-test-1-boxes.gltf'; @@ -286,7 +289,7 @@ defineSuite([ }); it('renders ECEF in 2D', function() { - return loadModel(boxesEcefUrl, { + return loadModel(boxEcefUrl, { modelMatrix : Matrix4.IDENTITY, minimumPixelSize : undefined }).then(function(m) { @@ -308,7 +311,7 @@ defineSuite([ }); it('renders ECEF in CV', function() { - return loadModel(boxesEcefUrl, { + return loadModel(boxEcefUrl, { modelMatrix : Matrix4.IDENTITY, minimumPixelSize : undefined }).then(function(m) { @@ -318,6 +321,54 @@ defineSuite([ }); }); + it('Renders x-up model', function() { + return loadJson(boxEcefUrl).then(function(gltf) { + // Model data is z-up. Edit the transform to be z-up to x-up. + gltf.nodes.node_transform.matrix = Matrix4.pack(Axis.Z_UP_TO_X_UP, new Array(16)); + + return loadModelJson(gltf, { + modelMatrix : Matrix4.IDENTITY, + upAxis : Axis.X + }).then(function(m) { + verifyRender(m); + expect(m.upAxis).toBe(Axis.X); + primitives.remove(m); + }); + }); + }); + + it('Renders y-up model', function() { + return loadJson(boxEcefUrl).then(function(gltf) { + // Model data is z-up. Edit the transform to be z-up to y-up. + gltf.nodes.node_transform.matrix = Matrix4.pack(Axis.Z_UP_TO_Y_UP, new Array(16)); + + return loadModelJson(gltf, { + modelMatrix : Matrix4.IDENTITY, + upAxis : Axis.Y + }).then(function(m) { + verifyRender(m); + expect(m.upAxis).toBe(Axis.Y); + primitives.remove(m); + }); + }); + }); + + it('Renders z-up model', function() { + return loadJson(boxEcefUrl).then(function(gltf) { + // Model data is z-up. Edit the transform to be the identity. + gltf.nodes.node_transform.matrix = Matrix4.pack(Matrix4.IDENTITY, new Array(16)); + + return loadModelJson(gltf, { + modelMatrix : Matrix4.IDENTITY, + upAxis : Axis.Z + }).then(function(m) { + verifyRender(m); + expect(m.upAxis).toBe(Axis.Z); + primitives.remove(m); + }); + }); + }); + it('resolves readyPromise', function() { return texturedBoxModel.readyPromise.then(function(model) { verifyRender(model);