ThreeJs入门24-VTKLoader的内部加载过程解析

1,340 阅读4分钟

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战

示例代码采用three.js-r73版本: cdnjs.cloudflare.com/ajax/libs/t…

yuque_diagram (1).jpg

我们已经实战了VTKLoader来加载我们的VTK模型,那么VTKLoader内部是怎么实现的呢,我们来一探究竟。

VTKLoader构造函数

THREE.VTKLoader = function (manager) {

	this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;

};

THREE.VTKLoader.prototype = {
	constructor: THREE.VTKLoader,
}
  • THREE.VTKLoader的原型上设置了构造函数,
    • 可以传入manager参数,这个参数可以自定义加载管理器
    • 如果没有传,就是THREE内部默认的加载管理器THREE.DefaultLoadingManager

THREE.DefaultLoadingManager

  • 我们来看下默认加载管理器做了什么事
THREE.DefaultLoadingManager = new THREE.LoadingManager();
  • 默认加载管理器是实例化的THREE.LoadingManager

THREE.LoadingManager

  • 这个函数中定义了一些变量
THREE.LoadingManager = function (onLoad, onProgress, onError) {
	// 外层 this 别名
	var scope = this;

	var isLoading = false,
		itemsLoaded = 0,  // 代表已经加载字节数
		itemsTotal = 0; // 代表加载字节总数

	this.onStart = undefined;  // 初始化时这个函数没有定义,需要重写
	this.onLoad = onLoad; // 加载完成的回调
	this.onProgress = onProgress; // 加载进度的回调
	this.onError = onError; // 加载错误的回调
  ....
}
  • 需要注意的是itemsLoadeditemsTotal记录的是字节数
  • 整个函数的生命周期是这样的
    • itemStart 加载之前
    • onStart 加载载开始的回调
    • itemEnd 加载结束
    • onLoad 加载完成的回调

itemStart 和 itemEnd 在每次服务器返回数据时调用

// 加载	url	 地址
this.itemStart = function (url) {
  // 总共加载字节数
  itemsTotal++;
  // 如果没有加载
  if (isLoading === false) {
    // 
    if (scope.onStart !== undefined) {
      // url 已经加载的数量 总共加载数量
      scope.onStart(url, itemsLoaded, itemsTotal);
    }
  }
  // 设置为正在加载
  isLoading = true;
};
  • itemStart每次调用会记录总共的加载字节数的总数
  • 如果没有加载,并且有onStart回调,就调用这个方法
  • 把记载状态记为加载中
this.itemEnd = function (url) {
  // 已经加载字节数
  itemsLoaded++;
  if (scope.onProgress !== undefined) {
    scope.onProgress(url, itemsLoaded, itemsTotal);
  }
  // 已经加载的字节数等于总的字节数
  if (itemsLoaded === itemsTotal) {
    isLoading = false;
    if (scope.onLoad !== undefined) {
      // 调用onLoad
      scope.onLoad();
    }
  }
};
  • itemEnd 会记录已经加载的字节数
  • 如果定义了onProgress回调函数,就调用
  • 如果已经加载的字节数等于了总的字节数,说明加载完成,调用onLoad回调函数

VTKLoader的load方法

  • 介绍完了构造函数,我们来看看VTKLoader的load方法
	/**
	 * 
	 * @param {*} url url地址
	 * @param {*} onLoad 加载成功回调
	 * @param {*} onProgress 加载过程回调
	 * @param {*} onError 加载错误回调
	 */
	load: function (url, onLoad, onProgress, onError) {

		var scope = this;
		// 调用THREE的XHR请求实例,传入了加载管理器
		var loader = new THREE.XHRLoader(scope.manager);
		// 设置cross
		loader.setCrossOrigin(this.crossOrigin);
		// 调用加载函数,返回文本数据
		loader.load(url, function (text) {
			// 通过parse解析文本数据,返回geometry
			onLoad(scope.parse(text));

		}, onProgress, onError);

	},
  • load方法很简单,主要是传入url地址,然后请求资源,调用对应的回调函数
  • 实例化THREE.XHRLoader,请求数据成功之后,返回文本数据,通过parse函数进行解析(下节再详细解释),返回geometry

THREE.XHRLoader

  • 这是THREE内部基于XHR定义的请求加载器
THREE.XHRLoader = function (manager) {
	this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;
};

THREE.XHRLoader.prototype = {
	constructor: THREE.XHRLoader,
  ...
}
  • THREE.XHRLoader的构造函数默认使用的也是THREE.DefaultLoadingManager
  • 主要看下load方法
load: function (url, onLoad, onProgress, onError) {
		var scope = this;
		// 获取缓存url的数据
		var cached = THREE.Cache.get(url);
		// 如果有缓存的数据,就直接走缓存缓存
		if (cached !== undefined) {
			if (onLoad) {
				setTimeout(function () {
					onLoad(cached);
				}, 0);
			}
			return cached;
		}
		// 创建 XHR 的 get 请求
		var request = new XMLHttpRequest();
		request.open('GET', url, true);
		// 监听加载状态
		request.addEventListener('load', function (event) {
			// 获取到响应数据
			var response = event.target.response;
			// 把url对应的数据缓存起来
			THREE.Cache.add(url, response);
			// 调用 onLoad回调 返回数据
			if (onLoad) onLoad(response);
			// 调用加载结束方法
			scope.manager.itemEnd(url);

		}, false);
		// 如果我们定义了 onProgress 回调,监听加载进度
		if (onProgress !== undefined) {
			request.addEventListener('progress', function (event) {
				onProgress(event);
			}, false);
		}
		// 监听加载错误
		request.addEventListener('error', function (event) {
			// 如果定义了 onError回调 就返回对应的数据
			if (onError) onError(event);
      scope.manager.itemError(url);
    }, false);
		// 设置 cross
		if (this.crossOrigin !== undefined) request.crossOrigin = this.crossOrigin;
		// 设置响应类型
		if (this.responseType !== undefined) request.responseType = this.responseType;
		// 设置跨域是否携带凭据
		if (this.withCredentials !== undefined) request.withCredentials = this.withCredentials;
		// 发送请求
		request.send(null);
		// 调用 itemStart 生命周期
		scope.manager.itemStart(url);
		return request;
	}
  • 先是获取缓存数据,如果有缓存数据,就直接返回缓存数据
  • 然后通过创建 XHR 的 get 请求,获取数据,把获取的数据缓存起来
  • 调用生命周期方法,完成回调通知

THREE.Cache

  • three缓存方法实现
THREE.Cache = {
	enabled: false, // 是否开启缓存
	files: {}, // 缓存数据
	add: function (key, file) {
		if (this.enabled === false) return;
		this.files[key] = file;
	},
	get: function (key) {
		if (this.enabled === false) return;
		return this.files[key];
	},
	remove: function (key) {
		delete this.files[key];
	},
	clear: function () {
		this.files = {};
	}
};
  • enabled:是否开启缓存的开关
  • files:缓存的数据其实是一个局部作用域的object对象
  • 定义了添加(add),获取(get),移除(remove),清空(clear)方法

函数执行流程

  • 核心函数介绍完了,我们来看下整个VTKloader函数的执行流程

image.png

总结

这一节我们主要讲了以下内容:

  • VTKLoader构造函数
  • THREE.DefaultLoadingManager默认加载器
  • THREE.LoadingManager加载构造器
  • THREE.XHRLoader请求loader
  • THREE.Cache缓存实现
  • VTKLoader请求资源过程