2018-08-01 21:34:58 +08:00

1132 lines
23 KiB
JavaScript

/**
* @author alteredq / http://alteredqualia.com/
*/
THREE.SceneLoader = function (manager) {
this.onLoadStart = function () { };
this.onLoadProgress = function () { };
this.onLoadComplete = function () { };
this.callbackSync = function () { };
this.callbackProgress = function () { };
this.geometryHandlers = {};
this.hierarchyHandlers = {};
this.addGeometryHandler("ascii", THREE.JSONLoader);
this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;
};
THREE.SceneLoader.prototype = {
constructor: THREE.SceneLoader,
load: function (url, onLoad, onProgress, onError) {
var scope = this;
var loader = new THREE.FileLoader(scope.manager);
loader.load(url, function (text) {
scope.parse(JSON.parse(text), onLoad, url);
}, onProgress, onError);
},
addGeometryHandler: function (typeID, loaderClass) {
this.geometryHandlers[typeID] = { "loaderClass": loaderClass };
},
addHierarchyHandler: function (typeID, loaderClass) {
this.hierarchyHandlers[typeID] = { "loaderClass": loaderClass };
},
parse: function (json, callbackFinished, url) {
var scope = this;
var urlBase = THREE.Loader.prototype.extractUrlBase(url);
var geometry, material, camera, fog,
texture, images, color,
light, hex, intensity,
counter_models, counter_textures,
total_models, total_textures,
result;
var target_array = [];
var data = json;
// async geometry loaders
for (var typeID in this.geometryHandlers) {
var loaderClass = this.geometryHandlers[typeID]["loaderClass"];
this.geometryHandlers[typeID]["loaderObject"] = new loaderClass();
}
// async hierachy loaders
for (var typeID in this.hierarchyHandlers) {
var loaderClass = this.hierarchyHandlers[typeID]["loaderClass"];
this.hierarchyHandlers[typeID]["loaderObject"] = new loaderClass();
}
counter_models = 0;
counter_textures = 0;
result = {
scene: new THREE.Scene(),
geometries: {},
face_materials: {},
materials: {},
textures: {},
objects: {},
cameras: {},
lights: {},
fogs: {},
empties: {},
groups: {}
};
if (data.transform) {
var position = data.transform.position,
rotation = data.transform.rotation,
scale = data.transform.scale;
if (position) {
result.scene.position.fromArray(position);
}
if (rotation) {
result.scene.rotation.fromArray(rotation);
}
if (scale) {
result.scene.scale.fromArray(scale);
}
if (position || rotation || scale) {
result.scene.updateMatrix();
result.scene.updateMatrixWorld();
}
}
function get_url(source_url, url_type) {
if (url_type == "relativeToHTML") {
return source_url;
} else {
return urlBase + source_url;
}
}
// toplevel loader function, delegates to handle_children
function handle_objects() {
handle_children(result.scene, data.objects);
}
// handle all the children from the loaded json and attach them to given parent
function handle_children(parent, children) {
var mat, dst, pos, rot, scl, quat;
for (var objID in children) {
// check by id if child has already been handled,
// if not, create new object
var object = result.objects[objID];
var objJSON = children[objID];
if (object === undefined) {
// meshes
if (objJSON.type && (objJSON.type in scope.hierarchyHandlers)) {
if (objJSON.loading === undefined) {
material = result.materials[objJSON.material];
objJSON.loading = true;
var loader = scope.hierarchyHandlers[objJSON.type]["loaderObject"];
// ColladaLoader
if (loader.options) {
loader.load(get_url(objJSON.url, data.urlBaseType), create_callback_hierachy(objID, parent, material, objJSON));
// UTF8Loader
// OBJLoader
} else {
loader.load(get_url(objJSON.url, data.urlBaseType), create_callback_hierachy(objID, parent, material, objJSON));
}
}
} else if (objJSON.geometry !== undefined) {
geometry = result.geometries[objJSON.geometry];
// geometry already loaded
if (geometry) {
material = result.materials[objJSON.material];
pos = objJSON.position;
rot = objJSON.rotation;
scl = objJSON.scale;
mat = objJSON.matrix;
quat = objJSON.quaternion;
// use materials from the model file
// if there is no material specified in the object
if (!objJSON.material) {
material = new THREE.MultiMaterial(result.face_materials[objJSON.geometry]);
}
// use materials from the model file
// if there is just empty face material
// (must create new material as each model has its own face material)
if ((material instanceof THREE.MultiMaterial) && material.materials.length === 0) {
material = new THREE.MultiMaterial(result.face_materials[objJSON.geometry]);
}
if (objJSON.skin) {
object = new THREE.SkinnedMesh(geometry, material);
} else if (objJSON.morph) {
object = new THREE.MorphAnimMesh(geometry, material);
if (objJSON.duration !== undefined) {
object.duration = objJSON.duration;
}
if (objJSON.time !== undefined) {
object.time = objJSON.time;
}
if (objJSON.mirroredLoop !== undefined) {
object.mirroredLoop = objJSON.mirroredLoop;
}
if (material.morphNormals) {
geometry.computeMorphNormals();
}
} else {
object = new THREE.Mesh(geometry, material);
}
object.name = objID;
if (mat) {
object.matrixAutoUpdate = false;
object.matrix.set(
mat[0], mat[1], mat[2], mat[3],
mat[4], mat[5], mat[6], mat[7],
mat[8], mat[9], mat[10], mat[11],
mat[12], mat[13], mat[14], mat[15]
);
} else {
object.position.fromArray(pos);
if (quat) {
object.quaternion.fromArray(quat);
} else {
object.rotation.fromArray(rot);
}
object.scale.fromArray(scl);
}
object.visible = objJSON.visible;
object.castShadow = objJSON.castShadow;
object.receiveShadow = objJSON.receiveShadow;
parent.add(object);
result.objects[objID] = object;
}
// lights
} else if (objJSON.type === "AmbientLight" || objJSON.type === "PointLight" ||
objJSON.type === "DirectionalLight" || objJSON.type === "SpotLight" ||
objJSON.type === "HemisphereLight") {
var color = objJSON.color;
var intensity = objJSON.intensity;
var distance = objJSON.distance;
var position = objJSON.position;
var rotation = objJSON.rotation;
switch (objJSON.type) {
case 'AmbientLight':
light = new THREE.AmbientLight(color);
break;
case 'PointLight':
light = new THREE.PointLight(color, intensity, distance);
light.position.fromArray(position);
break;
case 'DirectionalLight':
light = new THREE.DirectionalLight(color, intensity);
light.position.fromArray(objJSON.direction);
break;
case 'SpotLight':
light = new THREE.SpotLight(color, intensity, distance);
light.angle = objJSON.angle;
light.position.fromArray(position);
light.target.set(position[0], position[1] - distance, position[2]);
light.target.applyEuler(new THREE.Euler(rotation[0], rotation[1], rotation[2], 'XYZ'));
break;
case 'HemisphereLight':
light = new THREE.DirectionalLight(color, intensity, distance);
light.target.set(position[0], position[1] - distance, position[2]);
light.target.applyEuler(new THREE.Euler(rotation[0], rotation[1], rotation[2], 'XYZ'));
break;
}
parent.add(light);
light.name = objID;
result.lights[objID] = light;
result.objects[objID] = light;
// cameras
} else if (objJSON.type === "PerspectiveCamera" || objJSON.type === "OrthographicCamera") {
pos = objJSON.position;
rot = objJSON.rotation;
quat = objJSON.quaternion;
if (objJSON.type === "PerspectiveCamera") {
camera = new THREE.PerspectiveCamera(objJSON.fov, objJSON.aspect, objJSON.near, objJSON.far);
} else if (objJSON.type === "OrthographicCamera") {
camera = new THREE.OrthographicCamera(objJSON.left, objJSON.right, objJSON.top, objJSON.bottom, objJSON.near, objJSON.far);
}
camera.name = objID;
camera.position.fromArray(pos);
if (quat !== undefined) {
camera.quaternion.fromArray(quat);
} else if (rot !== undefined) {
camera.rotation.fromArray(rot);
} else if (objJSON.target) {
camera.lookAt(new THREE.Vector3().fromArray(objJSON.target));
}
parent.add(camera);
result.cameras[objID] = camera;
result.objects[objID] = camera;
// pure Object3D
} else {
pos = objJSON.position;
rot = objJSON.rotation;
scl = objJSON.scale;
quat = objJSON.quaternion;
object = new THREE.Object3D();
object.name = objID;
object.position.fromArray(pos);
if (quat) {
object.quaternion.fromArray(quat);
} else {
object.rotation.fromArray(rot);
}
object.scale.fromArray(scl);
object.visible = (objJSON.visible !== undefined) ? objJSON.visible : false;
parent.add(object);
result.objects[objID] = object;
result.empties[objID] = object;
}
if (object) {
if (objJSON.userData !== undefined) {
for (var key in objJSON.userData) {
var value = objJSON.userData[key];
object.userData[key] = value;
}
}
if (objJSON.groups !== undefined) {
for (var i = 0; i < objJSON.groups.length; i++) {
var groupID = objJSON.groups[i];
if (result.groups[groupID] === undefined) {
result.groups[groupID] = [];
}
result.groups[groupID].push(objID);
}
}
}
}
if (object !== undefined && objJSON.children !== undefined) {
handle_children(object, objJSON.children);
}
}
}
function handle_mesh(geo, mat, id) {
result.geometries[id] = geo;
result.face_materials[id] = mat;
handle_objects();
}
function handle_hierarchy(node, id, parent, material, obj) {
var p = obj.position;
var r = obj.rotation;
var q = obj.quaternion;
var s = obj.scale;
node.position.fromArray(p);
if (q) {
node.quaternion.fromArray(q);
} else {
node.rotation.fromArray(r);
}
node.scale.fromArray(s);
// override children materials
// if object material was specified in JSON explicitly
if (material) {
node.traverse(function (child) {
child.material = material;
});
}
// override children visibility
// with root node visibility as specified in JSON
var visible = (obj.visible !== undefined) ? obj.visible : true;
node.traverse(function (child) {
child.visible = visible;
});
parent.add(node);
node.name = id;
result.objects[id] = node;
handle_objects();
}
function create_callback_geometry(id) {
return function (geo, mat) {
geo.name = id;
handle_mesh(geo, mat, id);
counter_models -= 1;
scope.onLoadComplete();
async_callback_gate();
}
}
function create_callback_hierachy(id, parent, material, obj) {
return function (event) {
var result;
// loaders which use EventDispatcher
if (event.content) {
result = event.content;
// ColladaLoader
} else if (event.dae) {
result = event.scene;
// UTF8Loader
} else {
result = event;
}
handle_hierarchy(result, id, parent, material, obj);
counter_models -= 1;
scope.onLoadComplete();
async_callback_gate();
}
}
function create_callback_embed(id) {
return function (geo, mat) {
geo.name = id;
result.geometries[id] = geo;
result.face_materials[id] = mat;
}
}
function async_callback_gate() {
var progress = {
totalModels: total_models,
totalTextures: total_textures,
loadedModels: total_models - counter_models,
loadedTextures: total_textures - counter_textures
};
scope.callbackProgress(progress, result);
scope.onLoadProgress();
if (counter_models === 0 && counter_textures === 0) {
finalize();
callbackFinished(result);
}
}
function finalize() {
// take care of targets which could be asynchronously loaded objects
for (var i = 0; i < target_array.length; i++) {
var ta = target_array[i];
var target = result.objects[ta.targetName];
if (target) {
ta.object.target = target;
} else {
// if there was error and target of specified name doesn't exist in the scene file
// create instead dummy target
// (target must be added to scene explicitly as parent is already added)
ta.object.target = new THREE.Object3D();
result.scene.add(ta.object.target);
}
ta.object.target.userData.targetInverse = ta.object;
}
}
var callbackTexture = function (count) {
counter_textures -= count;
async_callback_gate();
scope.onLoadComplete();
};
// must use this instead of just directly calling callbackTexture
// because of closure in the calling context loop
var generateTextureCallback = function (count) {
return function () {
callbackTexture(count);
};
};
function traverse_json_hierarchy(objJSON, callback) {
callback(objJSON);
if (objJSON.children !== undefined) {
for (var objChildID in objJSON.children) {
traverse_json_hierarchy(objJSON.children[objChildID], callback);
}
}
}
// first go synchronous elements
// fogs
var fogID, fogJSON;
for (fogID in data.fogs) {
fogJSON = data.fogs[fogID];
if (fogJSON.type === "linear") {
fog = new THREE.Fog(0x000000, fogJSON.near, fogJSON.far);
} else if (fogJSON.type === "exp2") {
fog = new THREE.FogExp2(0x000000, fogJSON.density);
}
color = fogJSON.color;
fog.color.setRGB(color[0], color[1], color[2]);
result.fogs[fogID] = fog;
}
// now come potentially asynchronous elements
// geometries
// count how many geometries will be loaded asynchronously
var geoID, geoJSON;
for (geoID in data.geometries) {
geoJSON = data.geometries[geoID];
if (geoJSON.type in this.geometryHandlers) {
counter_models += 1;
scope.onLoadStart();
}
}
// count how many hierarchies will be loaded asynchronously
for (var objID in data.objects) {
traverse_json_hierarchy(data.objects[objID], function (objJSON) {
if (objJSON.type && (objJSON.type in scope.hierarchyHandlers)) {
counter_models += 1;
scope.onLoadStart();
}
});
}
total_models = counter_models;
for (geoID in data.geometries) {
geoJSON = data.geometries[geoID];
if (geoJSON.type === "cube") {
geometry = new THREE.BoxGeometry(geoJSON.width, geoJSON.height, geoJSON.depth, geoJSON.widthSegments, geoJSON.heightSegments, geoJSON.depthSegments);
geometry.name = geoID;
result.geometries[geoID] = geometry;
} else if (geoJSON.type === "plane") {
geometry = new THREE.PlaneGeometry(geoJSON.width, geoJSON.height, geoJSON.widthSegments, geoJSON.heightSegments);
geometry.name = geoID;
result.geometries[geoID] = geometry;
} else if (geoJSON.type === "sphere") {
geometry = new THREE.SphereGeometry(geoJSON.radius, geoJSON.widthSegments, geoJSON.heightSegments);
geometry.name = geoID;
result.geometries[geoID] = geometry;
} else if (geoJSON.type === "cylinder") {
geometry = new THREE.CylinderGeometry(geoJSON.topRad, geoJSON.botRad, geoJSON.height, geoJSON.radSegs, geoJSON.heightSegs);
geometry.name = geoID;
result.geometries[geoID] = geometry;
} else if (geoJSON.type === "torus") {
geometry = new THREE.TorusGeometry(geoJSON.radius, geoJSON.tube, geoJSON.segmentsR, geoJSON.segmentsT);
geometry.name = geoID;
result.geometries[geoID] = geometry;
} else if (geoJSON.type === "icosahedron") {
geometry = new THREE.IcosahedronGeometry(geoJSON.radius, geoJSON.subdivisions);
geometry.name = geoID;
result.geometries[geoID] = geometry;
} else if (geoJSON.type in this.geometryHandlers) {
var loader = this.geometryHandlers[geoJSON.type]["loaderObject"];
loader.load(get_url(geoJSON.url, data.urlBaseType), create_callback_geometry(geoID));
} else if (geoJSON.type === "embedded") {
var modelJson = data.embeds[geoJSON.id],
texture_path = "";
// pass metadata along to jsonLoader so it knows the format version
modelJson.metadata = data.metadata;
if (modelJson) {
var jsonLoader = this.geometryHandlers["ascii"]["loaderObject"];
var model = jsonLoader.parse(modelJson, texture_path);
create_callback_embed(geoID)(model.geometry, model.materials);
}
}
}
// textures
// count how many textures will be loaded asynchronously
var textureID, textureJSON;
for (textureID in data.textures) {
textureJSON = data.textures[textureID];
if (Array.isArray(textureJSON.url)) {
counter_textures += textureJSON.url.length;
for (var n = 0; n < textureJSON.url.length; n++) {
scope.onLoadStart();
}
} else {
counter_textures += 1;
scope.onLoadStart();
}
}
total_textures = counter_textures;
for (textureID in data.textures) {
textureJSON = data.textures[textureID];
if (textureJSON.mapping !== undefined && THREE[textureJSON.mapping] !== undefined) {
textureJSON.mapping = THREE[textureJSON.mapping];
}
var texture;
if (Array.isArray(textureJSON.url)) {
var count = textureJSON.url.length;
var urls = [];
for (var i = 0; i < count; i++) {
urls[i] = get_url(textureJSON.url[i], data.urlBaseType);
}
var loader = THREE.Loader.Handlers.get(urls[0]);
if (loader !== null) {
texture = loader.load(urls, generateTextureCallback(count));
if (textureJSON.mapping !== undefined)
texture.mapping = textureJSON.mapping;
} else {
texture = new THREE.CubeTextureLoader().load(urls, generateTextureCallback(count));
texture.mapping = textureJSON.mapping;
}
} else {
var fullUrl = get_url(textureJSON.url, data.urlBaseType);
var textureCallback = generateTextureCallback(1);
var loader = THREE.Loader.Handlers.get(fullUrl);
if (loader !== null) {
texture = loader.load(fullUrl, textureCallback);
} else {
texture = new THREE.Texture();
loader = new THREE.ImageLoader();
(function (texture) {
loader.load(fullUrl, function (image) {
texture.image = image;
texture.needsUpdate = true;
textureCallback();
});
})(texture)
}
if (textureJSON.mapping !== undefined)
texture.mapping = textureJSON.mapping;
if (THREE[textureJSON.minFilter] !== undefined)
texture.minFilter = THREE[textureJSON.minFilter];
if (THREE[textureJSON.magFilter] !== undefined)
texture.magFilter = THREE[textureJSON.magFilter];
if (textureJSON.anisotropy) texture.anisotropy = textureJSON.anisotropy;
if (textureJSON.repeat) {
texture.repeat.set(textureJSON.repeat[0], textureJSON.repeat[1]);
if (textureJSON.repeat[0] !== 1) texture.wrapS = THREE.RepeatWrapping;
if (textureJSON.repeat[1] !== 1) texture.wrapT = THREE.RepeatWrapping;
}
if (textureJSON.offset) {
texture.offset.set(textureJSON.offset[0], textureJSON.offset[1]);
}
// handle wrap after repeat so that default repeat can be overriden
if (textureJSON.wrap) {
var wrapMap = {
"repeat": THREE.RepeatWrapping,
"mirror": THREE.MirroredRepeatWrapping
};
if (wrapMap[textureJSON.wrap[0]] !== undefined) texture.wrapS = wrapMap[textureJSON.wrap[0]];
if (wrapMap[textureJSON.wrap[1]] !== undefined) texture.wrapT = wrapMap[textureJSON.wrap[1]];
}
}
result.textures[textureID] = texture;
}
// materials
var matID, matJSON;
var parID;
for (matID in data.materials) {
matJSON = data.materials[matID];
for (parID in matJSON.parameters) {
if (parID === "envMap" || parID === "map" || parID === "lightMap" || parID === "bumpMap" || parID === "normalMap" || parID === "alphaMap") {
matJSON.parameters[parID] = result.textures[matJSON.parameters[parID]];
} else if (parID === "shading") {
matJSON.parameters[parID] = (matJSON.parameters[parID] === "flat") ? THREE.FlatShading : THREE.SmoothShading;
} else if (parID === "side") {
if (matJSON.parameters[parID] == "double") {
matJSON.parameters[parID] = THREE.DoubleSide;
} else if (matJSON.parameters[parID] == "back") {
matJSON.parameters[parID] = THREE.BackSide;
} else {
matJSON.parameters[parID] = THREE.FrontSide;
}
} else if (parID === "blending") {
matJSON.parameters[parID] = matJSON.parameters[parID] in THREE ? THREE[matJSON.parameters[parID]] : THREE.NormalBlending;
} else if (parID === "combine") {
matJSON.parameters[parID] = matJSON.parameters[parID] in THREE ? THREE[matJSON.parameters[parID]] : THREE.MultiplyOperation;
} else if (parID === "vertexColors") {
if (matJSON.parameters[parID] == "face") {
matJSON.parameters[parID] = THREE.FaceColors;
// default to vertex colors if "vertexColors" is anything else face colors or 0 / null / false
} else if (matJSON.parameters[parID]) {
matJSON.parameters[parID] = THREE.VertexColors;
}
} else if (parID === "wrapRGB") {
var v3 = matJSON.parameters[parID];
matJSON.parameters[parID] = new THREE.Vector3(v3[0], v3[1], v3[2]);
} else if (parID === "normalScale") {
var v2 = matJSON.parameters[parID];
matJSON.parameters[parID] = new THREE.Vector2(v2[0], v2[1]);
}
}
if (matJSON.parameters.opacity !== undefined && matJSON.parameters.opacity < 1.0) {
matJSON.parameters.transparent = true;
}
material = new THREE[matJSON.type](matJSON.parameters);
material.name = matID;
result.materials[matID] = material;
}
// second pass through all materials to initialize MultiMaterials
// that could be referring to other materials out of order
for (matID in data.materials) {
matJSON = data.materials[matID];
if (matJSON.parameters.materials) {
var materialArray = [];
for (var i = 0; i < matJSON.parameters.materials.length; i++) {
var label = matJSON.parameters.materials[i];
materialArray.push(result.materials[label]);
}
result.materials[matID].materials = materialArray;
}
}
// objects ( synchronous init of procedural primitives )
handle_objects();
// defaults
if (result.cameras && data.defaults.camera) {
result.currentCamera = result.cameras[data.defaults.camera];
}
if (result.fogs && data.defaults.fog) {
result.scene.fog = result.fogs[data.defaults.fog];
}
// synchronous callback
scope.callbackSync(result);
// just in case there are no async elements
async_callback_gate();
}
};