three.js VTKLoader 中文注释

原文链接

vtk.js 简记

vtk.js 官网

vtk 官网

import {
    BufferAttribute, // three.js 中的 BufferAttribute 类,用于创建和管理缓冲区属性
    BufferGeometry, // three.js 中的 BufferGeometry 类,用于表示几何体
    Color, // three.js 中的 Color 类,用于表示和操作颜色
    FileLoader, // three.js 中的 FileLoader 类,用于加载文件
    Float32BufferAttribute, // three.js 中的 Float32BufferAttribute 类,用于创建 32 位浮点数缓冲区属性
    Loader, // three.js 中的 Loader 基类,所有加载器的基类
} from "three" // 从 three.js 模块中导入这些类
import * as fflate from "../libs/fflate.module.js" // 导入 fflate 库,用于处理压缩数据

class VTKLoader extends Loader {
    constructor(manager) {
        super(manager)
    }

    load(url, onLoad, onProgress, onError) { // 定义 load 方法,用于加载 VTK 文件
        const scope = this // 保存当前对象的引用
        const loader = new FileLoader(scope.manager) // 创建 FileLoader 实例
        loader.setPath(scope.path) // 设置文件路径
        loader.setResponseType("arraybuffer") // 设置响应类型为 arraybuffer
        loader.setRequestHeader(scope.requestHeader) // 设置请求头
        loader.setWithCredentials(scope.withCredentials) // 设置是否携带凭证
        loader.load(url, function(text) { // 加载文件并处理响应
            try {
                onLoad(scope.parse(text)) // 解析文件并调用 onLoad 回调函数
            } catch (e) {
                if (onError) {
                    onError(e) // 如果有错误,调用 onError 回调函数
                } else {
                    console.error(e) // 如果没有提供 onError 回调函数,则在控制台输出错误
                }
                scope.manager.itemError(url) // 通知管理器加载项出错
            }
        }, onProgress, onError) // 传递进度和错误回调函数
    }

    parse(data) {// 定义 parse 方法,用于解析 VTK 数据
        function parseASCII(data) {
            const indices = [] // 存储三角形顶点索引
            const positions = [] // 存储顶点坐标
            const colors = [] // 存储顶点颜色
            const normals = [] // 存储顶点法向量
            let result // 临时变量,用于存储正则匹配结果
            const patWord = /^[^\d.\s-]+/ // 匹配非数字、空白和负号的字符
            const pat3Floats = /(-?\d+.?[\d-+e]*)\s+(-?\d+.?[\d-+e]*)\s+(-?\d+.?[\d-+e]*)/g // 匹配三个浮点数
            const patConnectivity = /^(\d+)\s+([\s\d]*)/ // 匹配多边形连接信息
            const patPOINTS = /^POINTS / // 匹配 POINTS 标记
            const patPOLYGONS = /^POLYGONS / // 匹配 POLYGONS 标记
            const patTRIANGLE_STRIPS = /^TRIANGLE_STRIPS / // 匹配 TRIANGLE_STRIPS 标记
            const patPOINT_DATA = /^POINT_DATA[ ]+(\d+)/ // 匹配 POINT_DATA 标记
            const patCELL_DATA = /^CELL_DATA[ ]+(\d+)/ // 匹配 CELL_DATA 标记
            const patCOLOR_SCALARS = /^COLOR_SCALARS[ ]+(\w+)[ ]+3/ // 匹配 COLOR_SCALARS 标记
            const patNORMALS = /^NORMALS[ ]+(\w+)[ ]+(\w+)/ // 匹配 NORMALS 标记
            let inPointsSection = false // 标记是否在 POINTS 部分
            let inPolygonsSection = false // 标记是否在 POLYGONS 部分
            let inTriangleStripSection = false // 标记是否在 TRIANGLE_STRIPS 部分
            let inPointDataSection = false // 标记是否在 POINT_DATA 部分
            let inCellDataSection = false // 标记是否在 CELL_DATA 部分
            let inColorSection = false // 标记是否在 COLOR_SCALARS 部分
            let inNormalsSection = false // 标记是否在 NORMALS 部分
            const color = new Color() // 创建 Color 实例
            const lines = data.split("\n") // 按行分割数据

            for (const i in lines) { // 遍历每一行
                const line = lines[i].trim() // 去掉行首尾空白
                if (line.indexOf("DATASET") === 0) { // 如果行以 DATASET 开头
                    const dataset = line.split(" ")[1] // 获取数据集类型
                    if (dataset !== "POLYDATA") throw new Error("Unsupported DATASET type: " + dataset) // 如果不是 POLYDATA 类型,抛出错误
                } else if (inPointsSection) { // 如果在 POINTS 部分
                    while ((result = pat3Floats.exec(line)) !== null) { // 匹配三个浮点数
                        if (patWord.exec(line) !== null) break // 如果匹配到非数字字符,停止匹配
                        const x = parseFloat(result[1]) // 解析第一个浮点数
                        const y = parseFloat(result[2]) // 解析第二个浮点数
                        const z = parseFloat(result[3]) // 解析第三个浮点数
                        positions.push(x, y, z) // 将坐标添加到 positions 数组
                    }
                } else if (inPolygonsSection) { // 如果在 POLYGONS 部分
                    if ((result = patConnectivity.exec(line)) !== null) { // 匹配连接信息
                        const numVertices = parseInt(result[1]) // 获取顶点数
                        const inds = result[2].split(/\s+/) // 获取顶点索引
                        if (numVertices >= 3) { // 如果顶点数大于等于 3
                            const i0 = parseInt(inds[0]) // 第一个顶点索引
                            let k = 1 // 顶点索引偏移
                            for (let j = 0; j < numVertices - 2; ++j) { // 遍历多边形
                                const i1 = parseInt(inds[k]) // 第二个顶点索引
                                const i2 = parseInt(inds[k + 1]) // 第三个顶点索引
                                indices.push(i0, i1, i2) // 添加三角形顶点索引
                                k++
                            }
                        }
                    }
                } else if (inTriangleStripSection) { // 如果在 TRIANGLE_STRIPS 部分
                    if ((result = patConnectivity.exec(line)) !== null) { // 匹配连接信息
                        const numVertices = parseInt(result[1]) // 获取顶点数
                        const inds = result[2].split(/\s+/) // 获取顶点索引
                        if (numVertices >= 3) { // 如果顶点数大于等于 3
                            for (let j = 0; j < numVertices - 2; j++) { // 遍历三角带
                                if (j % 2 === 1) { // 如果是奇数索引
                                    const i0 = parseInt(inds[j]) // 第一个顶点索引
                                    const i1 = parseInt(inds[j + 2]) // 第二个顶点索引
                                    const i2 = parseInt(inds[j + 1]) // 第三个顶点索引
                                    indices.push(i0, i1, i2) // 添加三角形顶点索引
                                } else { // 如果是偶数索引
                                    const i0 = parseInt(inds[j]) // 第一个顶点索引
                                    const i1 = parseInt(inds[j + 1]) // 第二个顶点索引
                                    const i2 = parseInt(inds[j + 2]) // 第三个顶点索引
                                    indices.push(i0, i1, i2) // 添加三角形顶点索引
                                }
                            }
                        }
                    }
                } else if (inPointDataSection || inCellDataSection) { // 如果在 POINT_DATA 或 CELL_DATA 部分
                    if (inColorSection) { // 如果在 COLOR_SCALARS 部分
                        while ((result = pat3Floats.exec(line)) !== null) { // 匹配三个浮点数
                            if (patWord.exec(line) !== null) break // 如果匹配到非数字字符,停止匹配
                            const r = parseFloat(result[1]) // 解析红色分量
                            const g = parseFloat(result[2]) // 解析绿色分量
                            const b = parseFloat(result[3]) // 解析蓝色分量
                            color.set(r, g, b).convertSRGBToLinear() // 设置颜色并转换为线性颜色空间
                            colors.push(color.r, color.g, color.b) // 将颜色添加到 colors 数组
                        }
                    } else if (inNormalsSection) { // 如果在 NORMALS 部分
                        while ((result = pat3Floats.exec(line)) !== null) { // 匹配三个浮点数
                            if (patWord.exec(line) !== null) break // 如果匹配到非数字字符,停止匹配
                            const nx = parseFloat(result[1]) // 解析法向量的 x 分量
                            const ny = parseFloat(result[2]) // 解析法向量的 y 分量
                            const nz = parseFloat(result[3]) // 解析法向量的 z 分量
                            normals.push(nx, ny, nz) // 将法向量添加到 normals 数组
                        }
                    }
                }
                if (patPOLYGONS.exec(line) !== null) { // 匹配 POLYGONS 标记
                    inPolygonsSection = true // 设置为 POLYGONS 部分
                    inPointsSection = false // 取消 POINTS 部分
                    inTriangleStripSection = false // 取消 TRIANGLE_STRIPS 部分
                } else if (patPOINTS.exec(line) !== null) { // 匹配 POINTS 标记
                    inPolygonsSection = false // 取消 POLYGONS 部分
                    inPointsSection = true // 设置为 POINTS 部分
                    inTriangleStripSection = false // 取消 TRIANGLE_STRIPS 部分
                } else if (patTRIANGLE_STRIPS.exec(line) !== null) { // 匹配 TRIANGLE_STRIPS 标记
                    inPolygonsSection = false // 取消 POLYGONS 部分
                    inPointsSection = false // 取消 POINTS 部分
                    inTriangleStripSection = true // 设置为 TRIANGLE_STRIPS 部分
                } else if (patPOINT_DATA.exec(line) !== null) { // 匹配 POINT_DATA 标记
                    inPointDataSection = true // 设置为 POINT_DATA 部分
                    inPointsSection = false // 取消 POINTS 部分
                    inPolygonsSection = false // 取消 POLYGONS 部分
                    inTriangleStripSection = false // 取消 TRIANGLE_STRIPS 部分
                } else if (patCELL_DATA.exec(line) !== null) { // 匹配 CELL_DATA 标记
                    inCellDataSection = true // 设置为 CELL_DATA 部分
                    inPointsSection = false // 取消 POINTS 部分
                    inPolygonsSection = false // 取消 POLYGONS 部分
                    inTriangleStripSection = false // 取消 TRIANGLE_STRIPS 部分
                } else if (patCOLOR_SCALARS.exec(line) !== null) { // 匹配 COLOR_SCALARS 标记
                    inColorSection = true // 设置为 COLOR_SCALARS 部分
                    inNormalsSection = false // 取消 NORMALS 部分
                    inPointsSection = false // 取消 POINTS 部分
                    inPolygonsSection = false // 取消 POLYGONS 部分
                    inTriangleStripSection = false // 取消 TRIANGLE_STRIPS 部分
                } else if (patNORMALS.exec(line) !== null) { // 匹配 NORMALS 标记
                    inNormalsSection = true // 设置为 NORMALS 部分
                    inColorSection = false // 取消 COLOR_SCALARS 部分
                    inPointsSection = false // 取消 POINTS 部分
                    inPolygonsSection = false // 取消 POLYGONS 部分
                    inTriangleStripSection = false // 取消 TRIANGLE_STRIPS 部分
                }
            }
            let geometry = new BufferGeometry()
            geometry.setIndex(indices) // 设置几何体的索引
            geometry.setAttribute("position", new Float32BufferAttribute(positions, 3)) // 设置顶点位置属性
            if (normals.length === positions.length) {
                geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3)) // 设置法向量属性
            }
            if (colors.length !== indices.length) {
                if (colors.length === positions.length) {
                    geometry.setAttribute("color", new Float32BufferAttribute(colors, 3)) // 设置颜色属性
                }
            } else {
                geometry = geometry.toNonIndexed() // 将几何体转换为非索引几何体,即每个面都单独存储顶点数据
                const numTriangles = geometry.attributes.position.count / 3 // 计算三角形的数量(顶点数量除以 3)
                if (colors.length === (numTriangles * 3)) { // 如果颜色数组的长度等于三角形数量的三倍
                    const newColors = [] // 创建一个新的数组来存储展开后的颜色数据
                    for (let i = 0; i < numTriangles; i++) { // 遍历所有的三角形
                        const r = colors[3 * i + 0] // 获取颜色数组中第 i 个三角形的红色分量
                        const g = colors[3 * i + 1] // 获取颜色数组中第 i 个三角形的绿色分量
                        const b = colors[3 * i + 2] // 获取颜色数组中第 i 个三角形的蓝色分量

                        color.set(r, g, b).convertSRGBToLinear() // 设置颜色并转换为线性颜色空间
                        newColors.push(color.r, color.g, color.b) // 将线性颜色空间的红色分量添加到新颜色数组中
                        newColors.push(color.r, color.g, color.b) // 将线性颜色空间的绿色分量添加到新颜色数组中
                        newColors.push(color.r, color.g, color.b) // 将线性颜色空间的蓝色分量添加到新颜色数组中
                    }
                    geometry.setAttribute("color", new Float32BufferAttribute(newColors, 3)) // 设置几何体的颜色属性,使用新的颜色数组
                }
            }
            return geometry // 返回几何体
        }

        function parseBinary(data) {
            const buffer = new Uint8Array(data) // 创建一个 Uint8Array 缓冲区以存储二进制数据
            const dataView = new DataView(data) // 创建 DataView 以便更方便地读取二进制数据
            let points = [] // 存储顶点位置
            let normals = [] // 存储法向量
            let indices = [] // 存储顶点索引
            let index = 0 // 当前读取位置的索引

            // 查找字符串
            function findString(buffer, start) {
                let index = start // 从指定的起始位置开始
                let c = buffer[index] // 读取当前字节
                const s = [] // 用于存储字符串的字符
                while (c !== 10) { // 遇到换行符结束
                    s.push(String.fromCharCode(c)) // 将当前字节转换为字符并添加到数组中
                    index++ // 移动到下一个字节
                    c = buffer[index] // 读取下一个字节
                }
                return {
                    start: start, // 返回起始位置
                    end: index, // 返回结束位置
                    next: index + 1, // 返回下一个位置
                    parsedString: s.join(""), // 返回解析后的字符串
                }
            }

            let state, line
            while (true) {
                state = findString(buffer, index) // 查找当前索引位置的字符串
                line = state.parsedString // 获取解析后的字符串
                if (line.indexOf("DATASET") === 0) { // 如果行以 DATASET 开头
                    const dataset = line.split(" ")[1] // 获取数据集类型
                    if (dataset !== "POLYDATA") throw new Error("Unsupported DATASET type: " + dataset) // 如果不是 POLYDATA 类型,抛出错误
                } else if (line.indexOf("POINTS") === 0) { // 如果行以 POINTS 开头
                    const numberOfPoints = parseInt(line.split(" ")[1], 10) // 获取顶点数
                    const count = numberOfPoints * 4 * 3 // 计算数据长度(每个顶点由三个 32 位浮点数组成)
                    points = new Float32Array(numberOfPoints * 3) // 创建 Float32Array 用于存储顶点位置
                    let pointIndex = state.next // 获取下一个位置的索引
                    for (let i = 0; i < numberOfPoints; i++) { // 遍历所有顶点
                        points[3 * i] = dataView.getFloat32(pointIndex, false) // 读取 x 坐标
                        points[3 * i + 1] = dataView.getFloat32(pointIndex + 4, false) // 读取 y 坐标
                        points[3 * i + 2] = dataView.getFloat32(pointIndex + 8, false) // 读取 z 坐标
                        pointIndex = pointIndex + 12 // 移动到下一个顶点
                    }
                    state.next = state.next + count + 1 // 更新下一个位置的索引
                } else if (line.indexOf("TRIANGLE_STRIPS") === 0) { // 如果行以 TRIANGLE_STRIPS 开头
                    const numberOfStrips = parseInt(line.split(" ")[1], 10) // 获取三角带数量
                    const size = parseInt(line.split(" ")[2], 10) // 获取数据大小
                    const count = size * 4 // 计算数据长度
                    indices = new Uint32Array(3 * size - 9 * numberOfStrips) // 创建 Uint32Array 用于存储顶点索引
                    let indicesIndex = 0 // 索引数组的当前索引
                    let pointIndex = state.next // 获取下一个位置的索引
                    for (let i = 0; i < numberOfStrips; i++) { // 遍历所有三角带
                        const indexCount = dataView.getInt32(pointIndex, false) // 获取当前三角带的顶点数
                        const strip = [] // 用于存储三角带的顶点索引
                        pointIndex += 4 // 移动到下一个顶点
                        for (let s = 0; s < indexCount; s++) { // 遍历三角带中的所有顶点
                            strip.push(dataView.getInt32(pointIndex, false)) // 读取顶点索引并添加到数组中
                            pointIndex += 4 // 移动到下一个顶点
                        }
                        for (let j = 0; j < indexCount - 2; j++) { // 遍历三角带中的所有三角形
                            if (j % 2) { // 如果是奇数索引
                                indices[indicesIndex++] = strip[j] // 添加三角形的第一个顶点索引
                                indices[indicesIndex++] = strip[j + 2] // 添加三角形的第二个顶点索引
                                indices[indicesIndex++] = strip[j + 1] // 添加三角形的第三个顶点索引
                            } else { // 如果是偶数索引
                                indices[indicesIndex++] = strip[j] // 添加三角形的第一个顶点索引
                                indices[indicesIndex++] = strip[j + 1] // 添加三角形的第二个顶点索引
                                indices[indicesIndex++] = strip[j + 2] // 添加三角形的第三个顶点索引
                            }
                        }
                    }
                    state.next = state.next + count + 1 // 更新下一个位置的索引
                } else if (line.indexOf("POLYGONS") === 0) {
                    const numberOfStrips = parseInt(line.split(" ")[1], 10) // 获取多边形数量
                    const size = parseInt(line.split(" ")[2], 10) // 获取数据大小
                    const count = size * 4 // 计算数据长度
                    indices = new Uint32Array(3 * size - 9 * numberOfStrips) // 创建 Uint32Array 用于存储顶点索引
                    let indicesIndex = 0 // 索引数组的当前索引
                    let pointIndex = state.next // 获取下一个位置的索引
                    for (let i = 0; i < numberOfStrips; i++) { // 遍历所有多边形
                        const indexCount = dataView.getInt32(pointIndex, false) // 获取当前多边形的顶点数
                        const strip = [] // 用于存储多边形的顶点索引
                        pointIndex += 4 // 移动到下一个顶点
                        for (let s = 0; s < indexCount; s++) { // 遍历多边形中的所有顶点
                            strip.push(dataView.getInt32(pointIndex, false)) // 读取顶点索引并添加到数组中
                            pointIndex += 4 // 移动到下一个顶点
                        }
                        for (let j = 1; j < indexCount - 1; j++) { // 遍历多边形中的所有三角形
                            indices[indicesIndex++] = strip[0] // 添加三角形的第一个顶点索引
                            indices[indicesIndex++] = strip[j] // 添加三角形的第二个顶点索引
                            indices[indicesIndex++] = strip[j + 1] // 添加三角形的第三个顶点索引
                        }
                    }
                    state.next = state.next + count + 1 // 更新下一个位置的索引
                } else if (line.indexOf("POINT_DATA") === 0) {
                    const numberOfPoints = parseInt(line.split(" ")[1], 10) // 获取顶点数
                    state = findString(buffer, state.next) // 查找下一个字符串
                    const count = numberOfPoints * 4 * 3 // 计算数据长度
                    normals = new Float32Array(numberOfPoints * 3) // 创建 Float32Array 用于存储法向量
                    let pointIndex = state.next // 获取下一个位置的索引
                    for (let i = 0; i < numberOfPoints; i++) { // 遍历所有顶点
                        normals[3 * i] = dataView.getFloat32(pointIndex, false) // 读取法向量的 x 分量
                        normals[3 * i + 1] = dataView.getFloat32(pointIndex + 4, false) // 读取法向量的 y 分量
                        normals[3 * i + 2] = dataView.getFloat32(pointIndex + 8, false) // 读取法向量的 z 分量
                        pointIndex += 12 // 移动到下一个顶点
                    }
                    state.next = state.next + count // 更新下一个位置的索引
                }
                index = state.next // 更新当前索引位置
                if (index >= buffer.byteLength) { // 如果索引超过缓冲区长度,结束循环
                    break
                }
            }
            const geometry = new BufferGeometry() // 创建 BufferGeometry 实例
            geometry.setIndex(new BufferAttribute(indices, 1)) // 设置几何体的索引属性
            geometry.setAttribute("position", new BufferAttribute(points, 3)) // 设置顶点位置属性
            if (normals.length === points.length) {
                geometry.setAttribute("normal", new BufferAttribute(normals, 3)) // 设置法向量属性
            }
            return geometry // 返回几何体
        }

        // 连接两个 Float32Array
        function Float32Concat(first, second) {
            const firstLength = first.length, result = new Float32Array(firstLength + second.length) // 创建新数组,长度为两个数组之和
            result.set(first) // 设置第一个数组
            result.set(second, firstLength) // 设置第二个数组,从第一个数组的末尾开始
            return result // 返回连接后的新数组
        }

        // 连接两个 Int32Array
        function Int32Concat(first, second) {
            const firstLength = first.length, result = new Int32Array(firstLength + second.length) // 创建新数组,长度为两个数组之和
            result.set(first) // 设置第一个数组
            result.set(second, firstLength) // 设置第二个数组,从第一个数组的末尾开始
            return result // 返回连接后的新数组
        }

        function parseXML(stringFile) {
            // Changes XML to JSON, based on https://davidwalsh.name/convert-xml-json
            function xmlToJson(xml) { // 将 XML 转换为 JSON 对象
                let obj = {} // 创建空对象
                if (xml.nodeType === 1) { // 如果是元素节点
                    if (xml.attributes) { // 如果有属性
                        if (xml.attributes.length > 0) {
                            obj["attributes"] = {} // 创建属性对象
                            for (let j = 0; j < xml.attributes.length; j++) { // 遍历所有属性
                                const attribute = xml.attributes.item(j) // 获取属性
                                obj["attributes"][attribute.nodeName] = attribute.nodeValue.trim() // 将属性添加到对象中
                            }
                        }
                    }
                } else if (xml.nodeType === 3) { // 如果是文本节点
                    obj = xml.nodeValue.trim() // 获取文本值
                }
                if (xml.hasChildNodes()) { // 如果有子节点
                    for (let i = 0; i < xml.childNodes.length; i++) { // 遍历所有子节点
                        const item = xml.childNodes.item(i) // 获取子节点
                        const nodeName = item.nodeName // 获取节点名称
                        if (typeof obj[nodeName] === "undefined") {
                            const tmp = xmlToJson(item) // 递归解析子节点
                            if (tmp !== "") {
                                if (Array.isArray(tmp["#text"])) {
                                    tmp["#text"] = tmp["#text"][0] // 可能存在子节点 既有文本又有dom节点的情况
                                }
                                obj[nodeName] = tmp // 如果子节点不是空字符串,将其添加到对象中
                            }
                        } else {
                            if (typeof obj[nodeName].push === "undefined") {
                                const old = obj[nodeName] // 获取旧值
                                obj[nodeName] = [old] // 将旧值转换为数组
                            }
                            const tmp = xmlToJson(item) // 递归解析子节点
                            if (tmp !== "") {
                                if (Array.isArray(tmp["#text"])) {
                                    tmp["#text"] = tmp["#text"][0] // 可能存在子节点 既有文本又有dom节点的情况
                                }
                                obj[nodeName].push(tmp) // 将子节点添加到数组中
                            }
                        }
                    }
                }
                return obj // 返回解析后的对象
            }

            // Taken from Base64-js
            function Base64toByteArray(b64) { // 将 Base64 编码的字符串转换为字节数组
                const Arr = typeof Uint8Array !== "undefined" ? Uint8Array : Array // 确定使用 Uint8Array 或普通数组
                const revLookup = [] // 创建反向查找表
                const code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" // Base64 编码字符表
                for (let i = 0, l = code.length; i < l; ++i) {
                    revLookup[code.charCodeAt(i)] = i // 填充反向查找表
                }
                revLookup["-".charCodeAt(0)] = 62 // 填充特殊字符
                revLookup["_".charCodeAt(0)] = 63 // 填充特殊字符
                const len = b64.length // 获取 Base64 字符串长度
                if (len % 4 > 0) {
                    throw new Error("Invalid string. Length must be a multiple of 4") // 如果长度不是 4 的倍数,抛出错误
                }
                const placeHolders = b64[len - 2] === "=" ? 2 : b64[len - 1] === "=" ? 1 : 0 // 计算填充字符的数量
                const arr = new Arr(len * 3 / 4 - placeHolders) // 创建字节数组
                const l = placeHolders > 0 ? len - 4 : len // 计算有效字符的长度
                let L = 0
                let i, j
                for (i = 0, j = 0; i < l; i += 4, j += 3) { // 遍历 Base64 字符串
                    const tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)] // 将 4 个 Base64 字符转换为 3 个字节
                    arr[L++] = (tmp & 0xFF0000) >> 16 // 提取第一个字节
                    arr[L++] = (tmp & 0xFF00) >> 8 // 提取第二个字节
                    arr[L++] = tmp & 0xFF // 提取第三个字节
                }
                if (placeHolders === 2) { // 如果有两个填充字符
                    const tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4) // 将 2 个 Base64 字符转换为 1 个字节
                    arr[L++] = tmp & 0xFF // 提取字节
                } else if (placeHolders === 1) { // 如果有一个填充字符
                    const tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2) // 将 3 个 Base64 字符转换为 2 个字节
                    arr[L++] = (tmp >> 8) & 0xFF // 提取第一个字节
                    arr[L++] = tmp & 0xFF // 提取第二个字节
                }
                return arr // 返回字节数组
            }

            function parseDataArray(ele, compressed) {
                let numBytes = 0
                if (json.attributes.header_type === "UInt64") { // 根据 header_type 确定字节数
                    numBytes = 8
                } else if (json.attributes.header_type === "UInt32") {
                    numBytes = 4
                }
                let txt, content
                // Check the format
                if (ele.attributes.format === "binary" && compressed) {
                    if (ele.attributes.type === "Float32") {
                        txt = new Float32Array() // 创建空的 Float32Array
                    } else if (ele.attributes.type === "Int32" || ele.attributes.type === "Int64") {
                        txt = new Int32Array() // 创建空的 Int32Array
                    }
                    // 带标头的 VTP 数据具有以下结构:
                    // [#blocks][#u-size][#p-size][#c-size-1][#c-size-2]...[#c-size-#blocks][DATA]
                    //
                    // 每个标记都是一个整数值,其类型由文件顶部的“header_type”指定(如果未指定类型,则为 UInt32)。标记含义如下:
                    // [#blocks] = 块数
                    // [#u-size] = 压缩前的块大小
                    // [#p-size] = 最后部分块的大小(如果不需要,则为零)
                    // [#c-size-i] = 压缩后块 i 的大小(以字节为单位)
                    //
                    // [DATA] 部分连续存储每个附加在一起的块。从数据部分开头到块开头的偏移量
                    // 根据标头,通过对前面块的压缩块大小求和来计算。
                    const textNode = ele["#text"]
                    const rawData = Array.isArray(textNode) ? textNode[0] : textNode // 获取原始数据
                    const byteData = Base64toByteArray(rawData) // 将 Base64 编码转换为字节数组
                    // 无论 header_type 如何, 每个数据点由 8 位组成
                    const dataPointSize = 8 // 数据点大小
                    let blocks = byteData[0] // 读取压缩块的数量
                    for (let i = 1; i < numBytes - 1; i++) {
                        blocks = blocks | (byteData[i] << (i * dataPointSize)) // 计算块数
                    }
                    let headerSize = (blocks + 3) * numBytes  // 计算头部大小
                    const padding = ((headerSize % 3) > 0) ? 3 - (headerSize % 3) : 0  // 计算填充量
                    headerSize = headerSize + padding // 添加填充量到头部大小
                    const dataOffsets = [] // 存储数据块的偏移量
                    let currentOffset = headerSize // 当前偏移量
                    dataOffsets.push(currentOffset) // 添加当前偏移量到数组
                    // 获取压缩后的块大小。
                    // c-size-i 之前有三个块,因此我们跳过 3*numBytes
                    const cSizeStart = 3 * numBytes // 压缩块大小的起始位置
                    for (let i = 0; i < blocks; i++) {
                        let currentBlockSize = byteData[i * numBytes + cSizeStart]  // 读取当前块的大小
                        for (let j = 1; j < numBytes - 1; j++) { // 遍历字节以计算当前块的大小
                            currentBlockSize = currentBlockSize | (byteData[i * numBytes + cSizeStart + j] << (j * dataPointSize))
                        }
                        currentOffset = currentOffset + currentBlockSize
                        dataOffsets.push(currentOffset)
                    }
                    for (let i = 0; i < dataOffsets.length - 1; i++) { // 遍历所有数据块的偏移量
                        const data = fflate.unzlibSync(byteData.slice(dataOffsets[i], dataOffsets[i + 1]))  // 解压缩数据块
                        content = data.buffer // 获取数据缓冲区
                        if (ele.attributes.type === "Float32") {  // 如果数据类型是 Float32
                            content = new Float32Array(content)  // 创建 Float32Array
                            txt = Float32Concat(txt, content) // 连接浮点数组
                        } else if (ele.attributes.type === "Int32" || ele.attributes.type === "Int64") { // 如果数据类型是 Int32 或 Int64
                            content = new Int32Array(content) // 创建 Int32Array
                            txt = Int32Concat(txt, content) // 连接整数数组
                        }
                    }
                    delete ele["#text"] // 删除元素的文本内容,以便后续处理
                    if (ele.attributes.type === "Int64") {
                        if (ele.attributes.format === "binary") {
                            txt = txt.filter(function(el, idx) {
                                if (idx % 2 !== 1) return true
                            })
                        }
                    }
                } else {
                    if (ele.attributes.format === "binary" && !compressed) { // 如果数据格式是二进制且未压缩
                        content = Base64toByteArray(ele["#text"]) // 将 Base64 字符串转换为字节数组
                        // 未压缩情况下的 VTP 数据具有以下结构:
                        // [#bytes][DATA]
                        // 其中“[#bytes]”是一个整数值,指定其后的数据块中的字节数。
                        content = content.slice(numBytes).buffer // 移除头部的字节数信息
                    } else {
                        if (ele["#text"]) { // 如果存在文本内容
                            content = ele["#text"].split(/\s+/).filter(function(el) { // 分割并过滤空字符串
                                if (el !== "") return el // 只保留非空字符串
                            })
                        } else {
                            content = new Int32Array(0).buffer // 如果没有文本内容,创建一个空的 Int32Array 缓冲区
                        }
                    }
                    delete ele["#text"] // 删除元素的文本内容,以便后续处理
                    // Get the content and optimize it
                    if (ele.attributes.type === "Float32") { // 如果数据类型是 Float32
                        txt = new Float32Array(content) // 创建一个 Float32Array 来存储数据
                    } else if (ele.attributes.type === "Int32") { // 如果数据类型是 Int32
                        txt = new Int32Array(content) // 创建一个 Int32Array 来存储数据
                    } else if (ele.attributes.type === "Int64") { // 如果数据类型是 Int64
                        txt = new Int32Array(content) // 创建一个 Int32Array 来存储数据
                        if (ele.attributes.format === "binary") { // 如果数据格式是二进制
                            txt = txt.filter(function(el, idx) { // 过滤数据
                                if (idx % 2 !== 1) return true // 只保留偶数索引的数据
                            })
                        }
                    }
                } // endif ( ele.attributes.format === 'binary' && compressed )
                return txt // 返回解析后的数据
            }

            // 主要部分
            // 获取 DOM
            const dom = new DOMParser().parseFromString(stringFile, "application/xml") // 将 XML 字符串解析为 DOM
            // 获取文档根元素
            const doc = dom.documentElement
            // 将 XML 转换为 JSON
            const json = xmlToJson(doc)
            let points = [] // 存储顶点位置
            let normals = [] // 存储法向量
            let indices = [] // 存储顶点索引
            if (json.AppendedData) {
                const appendedData = json.AppendedData["#text"].slice(1)
                const encoding = json.AppendedData.attributes.encoding
                const piece = json.PolyData.Piece // 获取 Piece 元素
                const sections = ["PointData", "CellData", "Points", "Verts", "Lines", "Strips", "Polys"]
                let sectionIndex = 0 // 当前部分的索引

                const offsets = sections.map(s => {
                    const sect = piece[s]
                    if (sect && sect.DataArray) {
                        const arr = Array.isArray(sect.DataArray) ? sect.DataArray : [sect.DataArray]
                        return arr.map(a => a.attributes.offset)
                    }
                    return []
                }).flat()
                for (const sect of sections) {
                    const section = piece[sect] // 获取当前部分
                    // 如果存在 DataArray
                    if (section && section.DataArray) {
                        if (Array.isArray(section.DataArray)) {
                            for (const sectionEle of section.DataArray) {
                                sectionEle["#text"] = appendedData.slice(offsets[sectionIndex], offsets[sectionIndex + 1])
                                sectionEle.attributes.format = "binary"
                                sectionIndex++
                            }
                        } else {
                            section.DataArray["#text"] = appendedData.slice(offsets[sectionIndex], offsets[sectionIndex + 1])
                            section.DataArray.attributes.format = "binary"
                            sectionIndex++
                        }
                    }
                }
            }
            if (json.PolyData) {
                const piece = json.PolyData.Piece // 获取 Piece 元素
                const compressed = json.attributes.hasOwnProperty("compressor") // 检查是否压缩
                // 可以优化
                // 遍历各个部分
                const sections = ["PointData", "Points", "Strips", "Polys"] // 定义需要处理的部分
                let sectionIndex = 0 // 当前部分的索引
                const numberOfSections = sections.length // 部分的数量
                while (sectionIndex < numberOfSections) { // 遍历所有部分
                    const section = piece[sections[sectionIndex]] // 获取当前部分
                    // 如果存在 DataArray
                    if (section && section.DataArray) {
                        // 根据 DataArray 的数量处理
                        let arr
                        if (Array.isArray(section.DataArray)) { // 如果 DataArray 是数组
                            arr = section.DataArray
                        } else {
                            arr = [section.DataArray] // 如果只有一个 DataArray,将其转换为数组
                        }
                        let dataArrayIndex = 0 // 当前 DataArray 的索引
                        const numberOfDataArrays = arr.length // DataArray 的数量
                        while (dataArrayIndex < numberOfDataArrays) { // 遍历所有 DataArray
                            // 解析 DataArray
                            if (("#text" in arr[dataArrayIndex]) && (arr[dataArrayIndex]["#text"].length > 0)) {
                                arr[dataArrayIndex].text = parseDataArray(arr[dataArrayIndex], compressed) // 解析 DataArray 的文本内容
                            }
                            dataArrayIndex++ // 移动到下一个 DataArray
                        }
                        switch (sections[sectionIndex]) {
                            case "PointData": { // 处理 PointData 部分
                                const numberOfPoints = parseInt(piece.attributes.NumberOfPoints) // 获取顶点数量
                                const normalsName = section.attributes.Normals // 获取法向量名称
                                if (numberOfPoints > 0) { // 如果顶点数量大于 0
                                    for (let i = 0, len = arr.length; i < len; i++) { // 遍历所有 DataArray
                                        if (normalsName === arr[i].attributes.Name) { // 如果名称匹配
                                            const components = arr[i].attributes.NumberOfComponents // 获取分量数量
                                            normals = new Float32Array(numberOfPoints * components) // 创建 Float32Array 存储法向量
                                            normals.set(arr[i].text, 0) // 设置法向量数据
                                        }
                                    }
                                }
                            }
                                break

                            case "Points": { // 处理 Points 部分
                                const numberOfPoints = parseInt(piece.attributes.NumberOfPoints) // 获取顶点数量
                                if (numberOfPoints > 0) { // 如果顶点数量大于 0
                                    const components = section.DataArray.attributes.NumberOfComponents // 获取分量数量
                                    points = new Float32Array(numberOfPoints * components) // 创建 Float32Array 存储顶点位置
                                    points.set(section.DataArray.text, 0) // 设置顶点位置数据
                                }
                            }
                                break

                            case "Strips": { // 处理 Strips 部分
                                const numberOfStrips = parseInt(piece.attributes.NumberOfStrips) // 获取三角带数量
                                if (numberOfStrips > 0) { // 如果三角带数量大于 0
                                    const connectivity = new Int32Array(section.DataArray[0].text.length) // 创建 Int32Array 存储连接信息
                                    const offset = new Int32Array(section.DataArray[1].text.length) // 创建 Int32Array 存储偏移信息
                                    connectivity.set(section.DataArray[0].text, 0) // 设置连接信息数据
                                    offset.set(section.DataArray[1].text, 0) // 设置偏移信息数据
                                    const size = numberOfStrips + connectivity.length // 计算大小
                                    indices = new Uint32Array(3 * size - 9 * numberOfStrips) // 创建 Uint32Array 存储顶点索引
                                    let indicesIndex = 0 // 索引数组的当前索引
                                    for (let i = 0, len = numberOfStrips; i < len; i++) { // 遍历所有三角带
                                        const strip = [] // 用于存储三角带的顶点索引
                                        for (let s = 0, len1 = offset[i], len0 = 0; s < len1 - len0; s++) { // 遍历三角带中的所有顶点
                                            strip.push(connectivity[s]) // 读取顶点索引并添加到数组中
                                            if (i > 0) len0 = offset[i - 1] // 更新偏移量
                                        }
                                        for (let j = 0, len1 = offset[i], len0 = 0; j < len1 - len0 - 2; j++) { // 遍历三角带中的所有三角形
                                            if (j % 2) { // 如果是奇数索引
                                                indices[indicesIndex++] = strip[j] // 添加三角形的第一个顶点索引
                                                indices[indicesIndex++] = strip[j + 2] // 添加三角形的第二个顶点索引
                                                indices[indicesIndex++] = strip[j + 1] // 添加三角形的第三个顶点索引
                                            } else { // 如果是偶数索引
                                                indices[indicesIndex++] = strip[j] // 添加三角形的第一个顶点索引
                                                indices[indicesIndex++] = strip[j + 1] // 添加三角形的第二个顶点索引
                                                indices[indicesIndex++] = strip[j + 2] // 添加三角形的第三个顶点索引
                                            }
                                            if (i > 0) len0 = offset[i - 1] // 更新偏移量
                                        }
                                    }
                                }
                            }
                                break

                            case "Polys": { // 处理 Polys 部分
                                const numberOfPolys = parseInt(piece.attributes.NumberOfPolys) // 获取多边形数量
                                if (numberOfPolys > 0) { // 如果多边形数量大于 0
                                    const connectivity = new Int32Array(section.DataArray[0].text.length) // 创建 Int32Array 存储连接信息
                                    const offset = new Int32Array(section.DataArray[1].text.length) // 创建 Int32Array 存储偏移信息
                                    connectivity.set(section.DataArray[0].text, 0) // 设置连接信息数据
                                    offset.set(section.DataArray[1].text, 0) // 设置偏移信息数据
                                    const size = numberOfPolys + connectivity.length // 计算大小
                                    indices = new Uint32Array(3 * size - 9 * numberOfPolys) // 创建 Uint32Array 存储顶点索引
                                    let indicesIndex = 0, connectivityIndex = 0
                                    let i = 0, len0 = 0
                                    const len = numberOfPolys
                                    while (i < len) { // 遍历所有多边形
                                        const poly = [] // 用于存储多边形的顶点索引
                                        let s = 0
                                        const len1 = offset[i]
                                        while (s < len1 - len0) { // 遍历多边形中的所有顶点
                                            poly.push(connectivity[connectivityIndex++]) // 读取顶点索引并添加到数组中
                                            s++
                                        }
                                        let j = 1
                                        while (j < len1 - len0 - 1) { // 遍历多边形中的所有三角形
                                            indices[indicesIndex++] = poly[0] // 添加三角形的第一个顶点索引
                                            indices[indicesIndex++] = poly[j] // 添加三角形的第二个顶点索引
                                            indices[indicesIndex++] = poly[j + 1] // 添加三角形的第三个顶点索引
                                            j++
                                        }
                                        i++
                                        len0 = offset[i - 1] // 更新偏移量
                                    }
                                }
                            }
                                break
                            default:
                                break
                        }
                    }
                    sectionIndex++ // 移动到下一个部分
                }
                const geometry = new BufferGeometry() // 创建 BufferGeometry 实例
                geometry.setIndex(new BufferAttribute(indices, 1)) // 设置几何体的索引属性
                geometry.setAttribute("position", new BufferAttribute(points, 3)) // 设置顶点位置属性
                if (normals.length === points.length) {
                    geometry.setAttribute("normal", new BufferAttribute(normals, 3)) // 设置法向量属性
                }
                return geometry // 返回几何体
            } else {
                throw new Error("Unsupported DATASET type")
            }
        }

        const textDecoder = new TextDecoder() // 创建 TextDecoder 实例,用于解码文本
        // 获取文件的前 5 行,检查是否包含关键字 binary
        const meta = textDecoder.decode(new Uint8Array(data, 0, 250)).split("\n")
        if (meta[0].indexOf("xml") !== -1) {
            return parseXML(textDecoder.decode(data)) // 如果是 XML 文件,调用 parseXML 方法解析
        } else if (meta[2].includes("ASCII")) {
            return parseASCII(textDecoder.decode(data)) // 如果是 ASCII 文件,调用 parseASCII 方法解析
        } else {
            return parseBinary(data) // 否则调用 parseBinary 方法解析
        }
    }
}

export { VTKLoader } // 导出 VTKLoader 类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值