const _object_pattern = /^[og]\s*(.+)?/; const _material_library_pattern = /^mtllib /; const _material_use_pattern = /^usemtl /; const _map_use_pattern = /^usemap /; const _face_vertex_data_separator_pattern = /\s+/; const _color = new Cesium.Color(); function ParserState() { const state = { objects: [], object: {}, vertices: [], normals: [], colors: [], uvs: [], materials: {}, materialLibraries: [], startObject: function (name, fromDeclaration) { if (this.object && this.object.fromDeclaration === false) { this.object.name = name; this.object.fromDeclaration = (fromDeclaration !== false); return; } const previousMaterial = (this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined); if (this.object && typeof this.object._finalize === 'function') { this.object._finalize(true); } this.object = { name: name || '', fromDeclaration: (fromDeclaration !== false), geometry: { vertices: [], normals: [], colors: [], uvs: [], hasUVIndices: false }, materials: [], smooth: true, startMaterial: function (name, libraries) { const previous = this._finalize(false); if (previous && (previous.inherited || previous.groupCount <= 0)) { this.materials.splice(previous.index, 1); } const material = { index: this.materials.length, name: name || '', mtllib: (Array.isArray(libraries) && libraries.length > 0 ? libraries[libraries.length - 1] : ''), smooth: (previous !== undefined ? previous.smooth : this.smooth), groupStart: (previous !== undefined ? previous.groupEnd : 0), groupEnd: -1, groupCount: -1, inherited: false, clone: function (index) { const cloned = { index: (typeof index === 'number' ? index : this.index), name: this.name, mtllib: this.mtllib, smooth: this.smooth, groupStart: 0, groupEnd: -1, groupCount: -1, inherited: false }; cloned.clone = this.clone.bind(cloned); return cloned; } }; this.materials.push(material); return material; }, currentMaterial: function () { if (this.materials.length > 0) { return this.materials[this.materials.length - 1]; } return undefined; }, _finalize: function (end) { const lastMultiMaterial = this.currentMaterial(); if (lastMultiMaterial && lastMultiMaterial.groupEnd === -1) { lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; lastMultiMaterial.inherited = false; } if (end && this.materials.length > 1) { for (let mi = this.materials.length - 1; mi >= 0; mi--) { if (this.materials[mi].groupCount <= 0) { this.materials.splice(mi, 1); } } } if (end && this.materials.length === 0) { this.materials.push({ name: '', smooth: this.smooth }); } return lastMultiMaterial; } }; if (previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function') { const declared = previousMaterial.clone(0); declared.inherited = true; this.object.materials.push(declared); } this.objects.push(this.object); }, finalize: function () { if (this.object && typeof this.object._finalize === 'function') { this.object._finalize(true); } }, parseVertexIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 3) * 3; }, parseNormalIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 3) * 3; }, parseUVIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 2) * 2; }, addVertex: function (a, b, c) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); dst.push(src[b + 0], src[b + 1], src[b + 2]); dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addVertexPoint: function (a) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); }, addVertexLine: function (a) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); }, addNormal: function (a, b, c) { const src = this.normals; const dst = this.object.geometry.normals; dst.push(src[a + 0], src[a + 1], src[a + 2]); dst.push(src[b + 0], src[b + 1], src[b + 2]); dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addFaceNormal: function (a, b, c) { console.warn("addFaceNormal"); // const src = this.vertices; // const dst = this.object.geometry.normals; // _vA.fromArray( src, a ); // _vB.fromArray( src, b ); // _vC.fromArray( src, c ); // _cb.subVectors( _vC, _vB ); // _ab.subVectors( _vA, _vB ); // _cb.cross( _ab ); // _cb.normalize(); // dst.push( _cb.x, _cb.y, _cb.z ); // dst.push( _cb.x, _cb.y, _cb.z ); // dst.push( _cb.x, _cb.y, _cb.z ); }, addColor: function (a, b, c) { const src = this.colors; const dst = this.object.geometry.colors; if (src[a] !== undefined) dst.push(src[a + 0], src[a + 1], src[a + 2]); if (src[b] !== undefined) dst.push(src[b + 0], src[b + 1], src[b + 2]); if (src[c] !== undefined) dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addUV: function (a, b, c) { const src = this.uvs; const dst = this.object.geometry.uvs; dst.push(src[a + 0], src[a + 1]); dst.push(src[b + 0], src[b + 1]); dst.push(src[c + 0], src[c + 1]); }, addDefaultUV: function () { const dst = this.object.geometry.uvs; dst.push(0, 0); dst.push(0, 0); dst.push(0, 0); }, addUVLine: function (a) { const src = this.uvs; const dst = this.object.geometry.uvs; dst.push(src[a + 0], src[a + 1]); }, addFace: function (a, b, c, ua, ub, uc, na, nb, nc) { const vLen = this.vertices.length; let ia = this.parseVertexIndex(a, vLen); let ib = this.parseVertexIndex(b, vLen); let ic = this.parseVertexIndex(c, vLen); this.addVertex(ia, ib, ic); this.addColor(ia, ib, ic); if (na !== undefined && na !== '') { const nLen = this.normals.length; ia = this.parseNormalIndex(na, nLen); ib = this.parseNormalIndex(nb, nLen); ic = this.parseNormalIndex(nc, nLen); this.addNormal(ia, ib, ic); } else { this.addFaceNormal(ia, ib, ic); } if (ua !== undefined && ua !== '') { const uvLen = this.uvs.length; ia = this.parseUVIndex(ua, uvLen); ib = this.parseUVIndex(ub, uvLen); ic = this.parseUVIndex(uc, uvLen); this.addUV(ia, ib, ic); this.object.geometry.hasUVIndices = true; } else { this.addDefaultUV(); } }, addPointGeometry: function (vertices) { this.object.geometry.type = 'Points'; const vLen = this.vertices.length; for (let vi = 0, l = vertices.length; vi < l; vi++) { const index = this.parseVertexIndex(vertices[vi], vLen); this.addVertexPoint(index); this.addColor(index); } }, addLineGeometry: function (vertices, uvs) { this.object.geometry.type = 'Line'; const vLen = this.vertices.length; const uvLen = this.uvs.length; for (let vi = 0, l = vertices.length; vi < l; vi++) { this.addVertexLine(this.parseVertexIndex(vertices[vi], vLen)); } for (let uvi = 0, l = uvs.length; uvi < l; uvi++) { this.addUVLine(this.parseUVIndex(uvs[uvi], uvLen)); } } }; state.startObject('', false); return state; } class AModelLoader { constructor(context) { this.context = context; } /** * 异步调用 * @param {*} url */ Load(url) { //解析obj数据 return Cesium.Resource.fetchText(url).then((result) => { return this.Parse(result, url.substring(0, url.lastIndexOf('/') + 1)); }); } Parse(text, path) { const state = new ParserState(); if (text.indexOf('\r\n') !== -1) { text = text.replace(/\r\n/g, '\n'); } if (text.indexOf('\\\n') !== -1) { text = text.replace(/\\\n/g, ''); } const lines = text.split('\n'); let result = []; for (let i = 0, l = lines.length; i < l; i++) { const line = lines[i].trimStart(); if (line.length === 0) continue; const lineFirstChar = line.charAt(0); if (lineFirstChar === '#') continue; if (lineFirstChar === 'v') { const data = line.split(_face_vertex_data_separator_pattern); switch (data[0]) { case 'v': state.vertices.push( parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3]) ); if (data.length >= 7) { Cesium.Color.fromBytes( parseFloat(data[4]), parseFloat(data[5]), parseFloat(data[6]), 1, _color ); state.colors.push(_color.red, _color.green, _color.blue); } else { state.colors.push(undefined, undefined, undefined); } break; case 'vn': state.normals.push( parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3]) ); break; case 'vt': state.uvs.push( parseFloat(data[1]), parseFloat(data[2]) ); break; } } else if (lineFirstChar === 'f') { const lineData = line.slice(1).trim(); const vertexData = lineData.split(_face_vertex_data_separator_pattern); const faceVertices = []; for (let j = 0, jl = vertexData.length; j < jl; j++) { const vertex = vertexData[j]; if (vertex.length > 0) { const vertexParts = vertex.split('/'); faceVertices.push(vertexParts); } } const v1 = faceVertices[0]; for (let j = 1, jl = faceVertices.length - 1; j < jl; j++) { const v2 = faceVertices[j]; const v3 = faceVertices[j + 1]; state.addFace( v1[0], v2[0], v3[0], v1[1], v2[1], v3[1], v1[2], v2[2], v3[2] ); } } else if (lineFirstChar === 'l') { const lineParts = line.substring(1).trim().split(' '); let lineVertices = []; const lineUVs = []; if (line.indexOf('/') === -1) { lineVertices = lineParts; } else { for (let li = 0, llen = lineParts.length; li < llen; li++) { const parts = lineParts[li].split('/'); if (parts[0] !== '') lineVertices.push(parts[0]); if (parts[1] !== '') lineUVs.push(parts[1]); } } state.addLineGeometry(lineVertices, lineUVs); } else if (lineFirstChar === 'p') { const lineData = line.slice(1).trim(); const pointData = lineData.split(' '); state.addPointGeometry(pointData); } else if ((result = _object_pattern.exec(line)) !== null) { const name = (' ' + result[0].slice(1).trim()).slice(1); state.startObject(name); } else if (_material_use_pattern.test(line)) { state.object.startMaterial(line.substring(7).trim(), state.materialLibraries); } else if (_material_library_pattern.test(line)) { state.materialLibraries.push(line.substring(7).trim()); } else if (_map_use_pattern.test(line)) { console.warn('Rendering identifier "usemap" not supported. Textures must be defined in MTL files.'); } else if (lineFirstChar === 's') { result = line.split(' '); if (result.length > 1) { const value = result[1].trim().toLowerCase(); state.object.smooth = (value !== '0' && value !== 'off'); } else { state.object.smooth = true; } const material = state.object.currentMaterial(); if (material) material.smooth = state.object.smooth; } else { if (line === '\0') continue; console.warn('Unexpected line: "' + line + '"'); } } state.finalize(); const container = new Node(); const hasPrimitives = !(state.objects.length === 1 && state.objects[0].geometry.vertices.length === 0); if (hasPrimitives === true) { for (let i = 0, l = state.objects.length; i < l; i++) { const object = state.objects[i]; const geometry = object.geometry; const materials = object.materials; if (geometry.vertices.length === 0) continue; let mesh = new Mesh(this.context, geometry); for (let mi = 0, miLen = materials.length; mi < miLen; mi++) { const sourceMaterial = materials[mi]; const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_'; let material = state.materials[materialHash]; if (this.materials !== null) { console.log("material"); } if (material === undefined) { material = new Material(this.context, geometry, path, sourceMaterial.mtllib); material.name = sourceMaterial.name; material.flatShading = sourceMaterial.smooth ? false : true; state.materials[materialHash] = material; } mesh.setMaterial(material); } mesh.name = object.name; container.add(mesh); } } return container; } } class Material { constructor(context, geometry, path, mtllib) { this.context = context; this.ready = false; const canvas = document.createElement("canvas"); canvas.width = 512; //默认 canvas.height = 512; //默认 this.canvas = canvas; let promise = Cesium.Resource.fetchText(path + mtllib) .then(async (text) => { let result = []; const lines = text.split('\n'); for (let i = 0, l = lines.length; i < l; i++) { const line = lines[i].trimStart(); if (line.length === 0) continue; const t = line.split(' ')[0]; if (t === "map_Kd") { let map = line.split(' ')[1]; result.push({ diffusemap: await this.loadTexture(path + map) }) } } return Promise.all(result); }); //创建shader let vs = "attribute vec3 position;\n"; let fs = ""; let outVS = ""; let hasNormal = false; let hasVertexColors = false; let hasSt = false; if (geometry.normals.length > 0) { hasNormal = true; } //顶点色 if (geometry.colors.length > 0) { hasVertexColors = true; } // UV if (geometry.hasUVIndices === true) { hasSt = true; } if (hasNormal) { vs += "attribute vec3 normal;\n"; vs += "varying vec3 v_normal;\n"; fs += "varying vec3 v_normal;\n"; outVS += "v_normal = normal;\n"; } if (hasVertexColors) { vs += "attribute vec3 color;\n"; vs += "varying vec2 v_color;\n"; fs += "varying vec2 v_color;\n"; outVS += "v_color = color;\n"; } if (hasSt) { vs += "attribute vec2 uv;\n"; vs += "varying vec2 v_uv;\n"; fs += "varying vec2 v_uv;\n"; outVS += "v_uv = uv;\n"; } vs += ` void main() { gl_Position = czm_modelViewProjection * vec4(position, 1.); ${outVS} } `; fs += ` uniform sampler2D colorTexture; void main() { vec4 color = texture2D(colorTexture, v_uv); gl_FragColor = color; } `; this.program = Cesium.ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs }); this.uniformMap = {}; let that = this; promise.then((images) => { for (let i = 0; i < images.length; i++) { const element = images[i]; let diffusemap = element.diffusemap; this.uniformMap.colorTexture = () => { return diffusemap; }; } that.ready = true; }); } updateColorTexture(video, width, height) { if (this.ready && Cesium.defined(video.videojs)) { video.videojs.play(); let colorTexture = this.uniformMap.colorTexture(); if (video.playing && video.timeupdate) { if (width !== colorTexture.width || height !== colorTexture.height) { this.canvas.width = width; this.canvas.height = height; // 重新创建texture const canvasContext = this.canvas.getContext("2d"); canvasContext.drawImage( video.dom, 0, 0, video.width, video.height, 0, 0, this.canvas.width, this.canvas.height ); let texture = new Cesium.Texture({ context: this.context, source: this.canvas }); this.uniformMap.colorTexture = () => { return texture; } } const canvasContext = this.canvas.getContext("2d"); canvasContext.drawImage( video.dom, 0, 0, video.width, video.height, 0, 0, this.canvas.width, this.canvas.height ); this.uniformMap.colorTexture().copyFrom({ source: this.canvas }); } } } setCommand(drawCommand) { drawCommand.shaderProgram = this.program; drawCommand.uniformMap = this.uniformMap; } loadTexture(url) { return Cesium.Resource.fetchImage(url) .then((image) => { this.canvas.width = image.width; this.canvas.height = image.height; const canvasContext = this.canvas.getContext("2d"); canvasContext.drawImage( image, 0, 0, image.width, image.height, 0, 0, this.canvas.width, this.canvas.height, ); let texture = new Cesium.Texture({ context: this.context, source: this.canvas, sampler: Cesium.Sampler.NEAREST }); return texture; }); } } class Mesh { constructor(context, geometry) { this.name = undefined; this.geometry = geometry; const vaAttributes = []; let index = 0; this.material = undefined; //创建顶点索引 const vertexBuffer = Cesium.Buffer.createVertexBuffer({ context: context, typedArray: Cesium.ComponentDatatype.createTypedArray(Cesium.ComponentDatatype.FLOAT, geometry.vertices), usage: Cesium.BufferUsage.STATIC_DRAW }); vaAttributes.push({ index: index, enabled: true, vertexBuffer: vertexBuffer, componentDatatype: Cesium.ComponentDatatype.FLOAT, componentsPerAttribute: 3, normalize: false }); //法线 if (geometry.normals.length > 0) { index++; const normalBuffer = Cesium.Buffer.createVertexBuffer({ context: context, typedArray: Cesium.ComponentDatatype.createTypedArray(Cesium.ComponentDatatype.FLOAT, geometry.normals), usage: Cesium.BufferUsage.STATIC_DRAW }); vaAttributes.push({ index: index, enabled: true, vertexBuffer: normalBuffer, componentDatatype: Cesium.ComponentDatatype.FLOAT, componentsPerAttribute: 3, normalize: false }); } //顶点色 if (geometry.colors.length > 0) { index++; const colorBuffer = Cesium.Buffer.createVertexBuffer({ context: context, typedArray: Cesium.ComponentDatatype.createTypedArray(Cesium.ComponentDatatype.FLOAT, geometry.colors), usage: Cesium.BufferUsage.STATIC_DRAW }); vaAttributes.push({ index: index, enabled: true, vertexBuffer: colorBuffer, componentDatatype: Cesium.ComponentDatatype.FLOAT, componentsPerAttribute: 3, normalize: false }); } // UV if (geometry.hasUVIndices === true) { index++; const uvBuffer = Cesium.Buffer.createVertexBuffer({ context: context, typedArray: Cesium.ComponentDatatype.createTypedArray(Cesium.ComponentDatatype.FLOAT, geometry.uvs), usage: Cesium.BufferUsage.STATIC_DRAW }); vaAttributes.push({ index: index, enabled: true, vertexBuffer: uvBuffer, componentDatatype: Cesium.ComponentDatatype.FLOAT, componentsPerAttribute: 2, normalize: false }); } const vertexArray = new Cesium.VertexArray({ context: context, attributes: vaAttributes }); const renderState = Cesium.RenderState.fromCache({ cull: { enabled: false }, depthMask: true, depthTest: { enabled: true, } }); this.drawCommand = new Cesium.DrawCommand({ owner: this, primitiveType: Cesium.PrimitiveType.TRIANGLES, vertexArray: vertexArray, renderState: renderState, pass: Cesium.Pass.OPAQUE, // debugShowBoundingVolume: true }); } setMaterial(material) { this.material = material; material.setCommand(this.drawCommand); } update(frameState) { if (Cesium.defined(this.material)) { if (this.material.ready) { frameState.commandList.push(this.drawCommand); } } } updateVideo(camera, video, cullingVolume) { if (Cesium.defined(this.material)) { if (this.material.ready) { const visibility = cullingVolume.computeVisibility(this.drawCommand.boundingVolume); if (visibility >= 0 && this.material.ready) { //如果视频可见 //计算level // cam let distance = camera.distanceToBoundingSphere(this.drawCommand.boundingVolume); let width = video.width; let height = video.height; if (distance >= 20 && distance < 100) { width = video.width / 2; height = video.height / 2; } else if (distance >= 100) { width = video.width / 10; height = video.height / 10; } this.material.updateColorTexture(video, width, height) } else { if (video.videojs) { video.videojs.pause(); } } } } } } class Node { constructor() { this._modelMatrix = Cesium.Matrix4.IDENTITY; this.parent = null; this.children = []; this.video = { videojs: null, dom: null, playing: false, timeupdate: false, width: 0, height: 0 } } get modelMatrix() { return this._modelMatrix; } set modelMatrix(matrix) { this._modelMatrix = matrix.clone(); this.updateModelMatrix(); } updateModelMatrix() { for (let i = 0; i < this.children.length; i++) { const child = this.children[i]; child.drawCommand.modelMatrix = this._modelMatrix; //计算包围盒 const sphere = Cesium.BoundingSphere.fromVertices(child.geometry.vertices); let newMat = Cesium.Matrix4.multiplyByTranslation(this._modelMatrix, sphere.center, new Cesium.Matrix4()); sphere.center = Cesium.Matrix4.getTranslation(newMat, new Cesium.Cartesian3()); child.drawCommand.boundingVolume = sphere; } } setPosition(position) { Cesium.Matrix4.multiplyByTranslation(this._modelMatrix, position, this._modelMatrix); this.updateModelMatrix(); } add(object) { object.parent = this; this.children.push(object); } update(frameState) { let camera = frameState.camera; const cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, camera.upWC ); for (let i = 0; i < this.children.length; i++) { const child = this.children[i]; child.update(frameState); if (Cesium.defined(this.video.videojs)) { child.updateVideo(camera, this.video, cullingVolume); } } } /** * 设置视频 * @param {*} url */ setVideo(url) { this.video.playing = false; this.video.timeupdate = false; let videoType = /^.+\.m3u8$/.test(url) ? "application/x-mpegURL" : "video/mp4"; if (!Cesium.defined(this.video.videojs)) { // const video = document.createElement('video'); video.setAttribute("id", "video_" + Cesium.createGuid()); video.setAttribute('crossorigin', 'anonymous'); video.setAttribute('muted', ''); // video.muted = true; video.autoplay = true; video.loop = true; video.preload = "auto"; video.style.display = 'none'; video.width = 512; video.height = 512; video.style.objectFit = 'fill' this.video.videojs = videojs(video, { techOrder: ['html5'] }, () => { }); video.addEventListener('playing', () => { // console.log(video.videoWidth) // //获取video 宽高 this.video.width = video.videoWidth; this.video.height = video.videoHeight; this.video.playing = true; }, true); video.addEventListener('timeupdate', () => { this.video.timeupdate = true; }, true); this.video.dom = video; // document.body.appendChild(video) } this.video.videojs.src([{ src: url, type: videoType }]); this.video.videojs.play(); } }