mirror of
https://github.com/openglobus/openglobus.git
synced 2025-12-08 19:25:27 +00:00
partial implementation
This commit is contained in:
parent
d55bbc535c
commit
15fb55f897
BIN
sandbox/modelLoad/CesiumMilkTruck.glb
Normal file
BIN
sandbox/modelLoad/CesiumMilkTruck.glb
Normal file
Binary file not shown.
25
sandbox/modelLoad/dracoLoader.html
Normal file
25
sandbox/modelLoad/dracoLoader.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Draco loader sample</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="https://www.gstatic.com/draco/versioned/decoders/1.5.7/draco_decoder.js"
|
||||||
|
></script>
|
||||||
|
<script src="./dracoLoader.js" type="module"></script>
|
||||||
|
<link rel="stylesheet" href="../../css/og.css" type="text/css" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="width: 100%; height: 100%">
|
||||||
|
<canvas id="frame" style="width: 100%; height: 100%"> </canvas>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
253
sandbox/modelLoad/dracoLoader.js
Normal file
253
sandbox/modelLoad/dracoLoader.js
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
|
import {
|
||||||
|
Globe,
|
||||||
|
control,
|
||||||
|
Vector,
|
||||||
|
LonLat,
|
||||||
|
Entity,
|
||||||
|
OpenStreetMap,
|
||||||
|
EmptyTerrain,
|
||||||
|
RgbTerrain,
|
||||||
|
GlobusRgbTerrain,
|
||||||
|
Object3d,
|
||||||
|
mercator,
|
||||||
|
Bing,
|
||||||
|
GeoVideo,
|
||||||
|
XYZ,
|
||||||
|
utils,
|
||||||
|
PlanetCamera,
|
||||||
|
Framebuffer,
|
||||||
|
input,
|
||||||
|
Program,
|
||||||
|
Vec4,
|
||||||
|
Vec2,
|
||||||
|
GeoImage,
|
||||||
|
Renderer,
|
||||||
|
Vec3,
|
||||||
|
Mat4,
|
||||||
|
RenderNode,
|
||||||
|
EntityCollection,
|
||||||
|
scene,
|
||||||
|
Gltf
|
||||||
|
} from "../../lib/og.es.js";
|
||||||
|
|
||||||
|
let renderer = new Renderer("frame", {
|
||||||
|
msaa: 8,
|
||||||
|
controls: [new control.SimpleNavigation({ speed: 0.01 }), new control.GeoObjectEditor()],
|
||||||
|
autoActivate: true
|
||||||
|
});
|
||||||
|
|
||||||
|
class MyScene extends RenderNode {
|
||||||
|
constructor() {
|
||||||
|
super("MyScene");
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const baseObj = Object3d.createCube(0.4, 2, 0.4).translate(new Vec3(0, 1, 0)).setMaterial({
|
||||||
|
ambient: "#882a2a",
|
||||||
|
diffuse: "#fb3434",
|
||||||
|
shininess: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const frustumObj = Object3d.createFrustum(3, 2, 1).setMaterial({
|
||||||
|
ambient: "#236028",
|
||||||
|
diffuse: "#1cdd23",
|
||||||
|
shininess: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const cylinderObj = Object3d.createCylinder(1, 0, 1)
|
||||||
|
.applyMat4(new Mat4().setRotation(new Vec3(1, 0, 0), (90 * Math.PI) / 180))
|
||||||
|
.setMaterial({
|
||||||
|
ambient: "#773381",
|
||||||
|
diffuse: "#ef00ff",
|
||||||
|
shininess: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
let parentEntity = new Entity({
|
||||||
|
cartesian: new Vec3(0, 0, 0),
|
||||||
|
independentPicking: true
|
||||||
|
// geoObject: {
|
||||||
|
// color: "rgb(90,90,90)",
|
||||||
|
// scale: 1,
|
||||||
|
// instanced: true,
|
||||||
|
// tag: `baseObj`,
|
||||||
|
// object3d: baseObj
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
let childEntity = new Entity({
|
||||||
|
cartesian: new Vec3(0, 1, 0),
|
||||||
|
independentPicking: true,
|
||||||
|
relativePosition: true,
|
||||||
|
geoObject: {
|
||||||
|
color: "rgb(90,90,90)",
|
||||||
|
instanced: true,
|
||||||
|
tag: `frustumObj`,
|
||||||
|
object3d: frustumObj
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let childChildEntity = new Entity({
|
||||||
|
cartesian: new Vec3(0, 3, -1),
|
||||||
|
independentPicking: true,
|
||||||
|
relativePosition: true,
|
||||||
|
geoObject: {
|
||||||
|
color: "rgb(90,90,90)",
|
||||||
|
instanced: true,
|
||||||
|
tag: `cylinderObj`,
|
||||||
|
object3d: cylinderObj
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
childEntity.appendChild(childChildEntity);
|
||||||
|
parentEntity.appendChild(childEntity);
|
||||||
|
|
||||||
|
let collection = new EntityCollection({
|
||||||
|
entities: [parentEntity]
|
||||||
|
});
|
||||||
|
|
||||||
|
collection.addTo(this);
|
||||||
|
|
||||||
|
this.renderer.activeCamera.set(new Vec3(-4, 21, 23), new Vec3(1, 0, 0));
|
||||||
|
|
||||||
|
this.renderer.activeCamera.update();
|
||||||
|
DracoDecoderModule().then((decoderModule) => {
|
||||||
|
Gltf.connectDracoDecoderModule(decoderModule);
|
||||||
|
Gltf.loadGlb("./maxwell_the_cat.glb").then((gltf) => {
|
||||||
|
// const models = gltf.getObjects3d();
|
||||||
|
const entities = gltf.toEntities();
|
||||||
|
console.log("entities", entities);
|
||||||
|
const cat = entities[0];
|
||||||
|
cat.setScale(0.1);
|
||||||
|
childChildEntity.appendChild(entities[0]);
|
||||||
|
// childChildEntity.appendChild(
|
||||||
|
// new Entity({
|
||||||
|
// cartesian: new Vec3(0, 3, -1),
|
||||||
|
// independentPicking: true,
|
||||||
|
// relativePosition: true,
|
||||||
|
// geoObject: {
|
||||||
|
// color: "rgb(90,90,90)",
|
||||||
|
// instanced: true,
|
||||||
|
// tag: `circleObj`,
|
||||||
|
// object3d: models[0]
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
this.renderer.activeCamera.update();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gltf.loadGlb("./CesiumMilkTruck.glb").then((gltf) => {
|
||||||
|
// const entity = gltf.toEntities()[0];
|
||||||
|
// entity.setScale(1);
|
||||||
|
// console.log('truck entity', entity);
|
||||||
|
// childChildEntity.appendChild(entity);
|
||||||
|
// this.renderer.activeCamera.update();
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.addNodes([new scene.Axes(), new MyScene()]);
|
||||||
|
|
||||||
|
async function loadGLB(url) {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const buffer = response.arrayBuffer();
|
||||||
|
return await buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseGLB(arrayBuffer) {
|
||||||
|
const dv = new DataView(arrayBuffer);
|
||||||
|
const magic = dv.getUint32(0, true);
|
||||||
|
if (magic !== 0x46546c67) throw new Error("Not a valid GLB");
|
||||||
|
|
||||||
|
const jsonChunkLength = dv.getUint32(12, true);
|
||||||
|
const jsonChunkStart = 20;
|
||||||
|
const jsonChunk = new TextDecoder().decode(
|
||||||
|
new Uint8Array(arrayBuffer, jsonChunkStart, jsonChunkLength)
|
||||||
|
);
|
||||||
|
const gltf = JSON.parse(jsonChunk);
|
||||||
|
const chunks = [];
|
||||||
|
const binChunkStart = 20 + jsonChunkLength;
|
||||||
|
for (let i = 0; i < gltf.bufferViews.length; i++) {
|
||||||
|
const bufferView = gltf.bufferViews[i];
|
||||||
|
const offset = i + 1;
|
||||||
|
const start = binChunkStart + 8 * offset + bufferView.byteOffset;
|
||||||
|
chunks.push(arrayBuffer.slice(start, start + bufferView.byteLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { gltf, chunks };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDracoCompressedAccessor(gltf) {
|
||||||
|
for (const mesh of gltf.meshes) {
|
||||||
|
for (const primitive of mesh.primitives) {
|
||||||
|
if (primitive.extensions && primitive.extensions["KHR_draco_mesh_compression"]) {
|
||||||
|
return primitive.extensions["KHR_draco_mesh_compression"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeDraco(decoderModule, binChunk, gltf) {
|
||||||
|
console.log(gltf);
|
||||||
|
const byteOffset = 0; // optional: read from bufferView
|
||||||
|
const dracoBufferView = new Uint8Array(binChunk, byteOffset); // if bufferView.byteOffset is available, use it
|
||||||
|
const decoder = new decoderModule.Decoder();
|
||||||
|
const buffer = new decoderModule.DecoderBuffer();
|
||||||
|
buffer.Init(dracoBufferView, dracoBufferView.byteLength);
|
||||||
|
|
||||||
|
const geometryType = decoder.GetEncodedGeometryType(buffer);
|
||||||
|
if (geometryType !== decoderModule.TRIANGULAR_MESH) {
|
||||||
|
throw new Error("Not a mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
const mesh = new decoderModule.Mesh();
|
||||||
|
const status = decoder.DecodeBufferToMesh(buffer, mesh);
|
||||||
|
if (!status.ok() || mesh.ptr === 0) {
|
||||||
|
console.log(status.error_msg());
|
||||||
|
throw new Error("Failed to decode Draco mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
const posAttrId = decoder.GetAttributeId(mesh, decoderModule.POSITION);
|
||||||
|
const posAttr = decoder.GetAttribute(mesh, posAttrId);
|
||||||
|
const numPoints = mesh.num_points();
|
||||||
|
|
||||||
|
const pos = new decoderModule.DracoFloat32Array();
|
||||||
|
decoder.GetAttributeFloatForAllPoints(mesh, posAttr, pos);
|
||||||
|
|
||||||
|
const positions = new Float32Array(numPoints * 3);
|
||||||
|
for (let i = 0; i < positions.length; i++) {
|
||||||
|
positions[i] = pos.GetValue(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
decoderModule.destroy(pos);
|
||||||
|
decoderModule.destroy(mesh);
|
||||||
|
decoderModule.destroy(decoder);
|
||||||
|
decoderModule.destroy(buffer);
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const test = async () => {
|
||||||
|
// const arrayBuffer = await loadGLB("./CesiumMilkTruck.glb");
|
||||||
|
// const { gltf, chunks } = parseGLB(arrayBuffer);
|
||||||
|
// // const dracoExt = getDracoCompressedAccessor(gltf);
|
||||||
|
// console.log(gltf, chunks);
|
||||||
|
// // eslint-disable-next-line no-undef
|
||||||
|
// const decoderModule = await DracoDecoderModule();
|
||||||
|
// await decoderModule.ready;
|
||||||
|
// const positions = decodeDraco(decoderModule, chunks[2], gltf);
|
||||||
|
// console.log(positions);
|
||||||
|
// const buffer = new decoderModule.DecoderBuffer();
|
||||||
|
// buffer.Init(cat, cat.length);
|
||||||
|
// const decoder = new decoderModule.Decoder();
|
||||||
|
// const geometryType = decoder.GetEncodedGeometryType(buffer);
|
||||||
|
// console.log(decoderModule, geometryType);
|
||||||
|
// const gltf = await Gltf.loadGlb("./CesiumMilkTruck.glb");
|
||||||
|
// const objects = gltf.getObjects3d();
|
||||||
|
// console.log(objects);
|
||||||
|
// await Gltf.loadGlb("./maxwell_the_cat.glb");
|
||||||
|
};
|
||||||
|
|
||||||
|
test();
|
||||||
BIN
sandbox/modelLoad/maxwell_the_cat.glb
Normal file
BIN
sandbox/modelLoad/maxwell_the_cat.glb
Normal file
Binary file not shown.
@ -13,7 +13,7 @@ function getColor(color?: number[] | TypedArray | string): Float32Array {
|
|||||||
} else if (typeof color === 'string') {
|
} else if (typeof color === 'string') {
|
||||||
return htmlColorToFloat32Array(color);
|
return htmlColorToFloat32Array(color);
|
||||||
}
|
}
|
||||||
return new Float32Array([1.0, 1.0, 1.0, 1.0]);
|
return new Float32Array([0.5, 0.5, 0.5, 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColor3v(color?: NumberArray3 | TypedArray | string): Float32Array {
|
function getColor3v(color?: NumberArray3 | TypedArray | string): Float32Array {
|
||||||
@ -44,9 +44,12 @@ interface IObject3dParams {
|
|||||||
diffuse?: string | NumberArray3;
|
diffuse?: string | NumberArray3;
|
||||||
specular?: string | NumberArray3;
|
specular?: string | NumberArray3;
|
||||||
shininess?: number;
|
shininess?: number;
|
||||||
colorTexture?: string;
|
colorTextureSrc?: string;
|
||||||
normalTexture?: string;
|
normalTextureSrc?: string;
|
||||||
metallicRoughnessTexture?: string;
|
metallicRoughnessTextureSrc?: string;
|
||||||
|
colorTextureImage?: HTMLImageElement;
|
||||||
|
normalTextureImage?: HTMLImageElement;
|
||||||
|
metallicRoughnessTextureImage?: HTMLImageElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
type MaterialParams = Pick<IObject3dParams, 'ambient' | 'diffuse' | 'specular' | 'shininess'>;
|
type MaterialParams = Pick<IObject3dParams, 'ambient' | 'diffuse' | 'specular' | 'shininess'>;
|
||||||
@ -67,13 +70,15 @@ class Object3d {
|
|||||||
public diffuse: Float32Array;
|
public diffuse: Float32Array;
|
||||||
public specular: Float32Array;
|
public specular: Float32Array;
|
||||||
public shininess: number;
|
public shininess: number;
|
||||||
public colorTexture: string;
|
public colorTextureSrc: string | null;
|
||||||
public normalTexture: string;
|
public colorTextureImage: HTMLImageElement | null;
|
||||||
public metallicRoughnessTexture: string;
|
public normalTextureSrc: string | null;
|
||||||
|
public normalTextureImage: HTMLImageElement | null;
|
||||||
|
public metallicRoughnessTextureSrc: string | null;
|
||||||
|
public metallicRoughnessTextureImage: HTMLImageElement | null;
|
||||||
public center: Vec3;
|
public center: Vec3;
|
||||||
|
|
||||||
constructor(data: IObject3dParams = {}) {
|
constructor(data: IObject3dParams = {}) {
|
||||||
|
|
||||||
this._name = data.name || "noname";
|
this._name = data.name || "noname";
|
||||||
this._vertices = data.vertices || [];
|
this._vertices = data.vertices || [];
|
||||||
this._numVertices = this._vertices.length / 3;
|
this._numVertices = this._vertices.length / 3;
|
||||||
@ -90,9 +95,12 @@ class Object3d {
|
|||||||
this.diffuse = getColor3v(data.diffuse);
|
this.diffuse = getColor3v(data.diffuse);
|
||||||
this.specular = getColor3v(data.specular);
|
this.specular = getColor3v(data.specular);
|
||||||
this.shininess = data.shininess || 100;
|
this.shininess = data.shininess || 100;
|
||||||
this.colorTexture = data.colorTexture || "";
|
this.colorTextureSrc = data.colorTextureSrc || null;
|
||||||
this.normalTexture = data.normalTexture || "";
|
this.colorTextureImage = data.colorTextureImage || null;
|
||||||
this.metallicRoughnessTexture = data.metallicRoughnessTexture || "";
|
this.normalTextureSrc = data.normalTextureSrc || null;
|
||||||
|
this.normalTextureImage = data.normalTextureImage || null;
|
||||||
|
this.metallicRoughnessTextureSrc = data.metallicRoughnessTextureSrc || null;
|
||||||
|
this.metallicRoughnessTextureImage = data.metallicRoughnessTextureImage || null;
|
||||||
|
|
||||||
if (data.scale) {
|
if (data.scale) {
|
||||||
let s = data.scale;
|
let s = data.scale;
|
||||||
@ -629,9 +637,9 @@ class Object3d {
|
|||||||
specular: mat.specular,
|
specular: mat.specular,
|
||||||
shininess: mat.shininess,
|
shininess: mat.shininess,
|
||||||
color: mat.color,
|
color: mat.color,
|
||||||
colorTexture: baseUrl ? `${baseUrl}/${mat.colorTexture}` : mat.colorTexture,
|
colorTextureSrc: baseUrl ? `${baseUrl}/${mat.colorTexture}` : mat.colorTexture,
|
||||||
normalTexture: baseUrl ? `${baseUrl}/${mat.normalTexture}` : mat.normalTexture,
|
normalTextureSrc: baseUrl ? `${baseUrl}/${mat.normalTexture}` : mat.normalTexture,
|
||||||
metallicRoughnessTexture: baseUrl ? `${baseUrl}/${mat.metallicRoughnessTexture}` : mat.metallicRoughnessTexture
|
metallicRoughnessTextureSrc: baseUrl ? `${baseUrl}/${mat.metallicRoughnessTexture}` : mat.metallicRoughnessTexture
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -658,9 +666,9 @@ class Object3d {
|
|||||||
specular: mat.specular,
|
specular: mat.specular,
|
||||||
shininess: mat.shininess,
|
shininess: mat.shininess,
|
||||||
color: mat.color,
|
color: mat.color,
|
||||||
colorTexture: mat.colorTexture,
|
colorTextureSrc: mat.colorTexture,
|
||||||
normalTexture: mat.normalTexture,
|
normalTextureSrc: mat.normalTexture,
|
||||||
metallicRoughnessTexture: mat.metallicRoughnessTexture
|
metallicRoughnessTextureSrc: mat.metallicRoughnessTexture
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -113,7 +113,11 @@ class GeoObject {
|
|||||||
|
|
||||||
this._localPosition = new Vec3();
|
this._localPosition = new Vec3();
|
||||||
|
|
||||||
this._color = utils.createColorRGBA(options.color, new Vec4(0.15, 0.15, 0.15, 1.0));
|
this._color = utils.createColorRGBA(
|
||||||
|
options.color, options.object3d?.color
|
||||||
|
? new Vec4(...Array.from(options.object3d.color))
|
||||||
|
: new Vec4(0.15, 0.15, 0.15, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
this._handler = null;
|
this._handler = null;
|
||||||
this._handlerIndex = -1;
|
this._handlerIndex = -1;
|
||||||
|
|||||||
@ -122,28 +122,49 @@ export class GeoObjectHandler {
|
|||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setColorTextureTag(src: string, tag: string) {
|
public setColorTextureTag(src: string | HTMLImageElement, tag: string) {
|
||||||
const tagData = this._instanceDataMap.get(tag);
|
const tagData = this._instanceDataMap.get(tag);
|
||||||
if (tagData) {
|
if (tagData) {
|
||||||
tagData._colorTextureSrc = src;
|
if (typeof src === 'string') {
|
||||||
|
tagData._colorTextureSrc = src;
|
||||||
|
tagData._colorTextureImage = null;
|
||||||
|
}
|
||||||
|
if (src instanceof HTMLImageElement) {
|
||||||
|
tagData._colorTextureSrc = null;
|
||||||
|
tagData._colorTextureImage = src;
|
||||||
|
}
|
||||||
this._instanceDataMap.set(tag, tagData);
|
this._instanceDataMap.set(tag, tagData);
|
||||||
this._loadColorTexture(tagData);
|
this._loadColorTexture(tagData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setNormalTextureTag(src: string, tag: string) {
|
public setNormalTextureTag(src: string | HTMLImageElement, tag: string) {
|
||||||
const tagData = this._instanceDataMap.get(tag);
|
const tagData = this._instanceDataMap.get(tag);
|
||||||
if (tagData) {
|
if (tagData) {
|
||||||
tagData._normalTextureSrc = src;
|
if (typeof src === 'string') {
|
||||||
|
tagData._normalTextureSrc = src;
|
||||||
|
tagData._normalTextureImage = null;
|
||||||
|
}
|
||||||
|
if (src instanceof HTMLImageElement) {
|
||||||
|
tagData._normalTextureSrc = null;
|
||||||
|
tagData._normalTextureImage = src;
|
||||||
|
}
|
||||||
this._instanceDataMap.set(tag, tagData);
|
this._instanceDataMap.set(tag, tagData);
|
||||||
this._loadNormalTexture(tagData);
|
this._loadNormalTexture(tagData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setMetallicRoughnessTextureTag(src: string, tag: string) {
|
public setMetallicRoughnessTextureTag(src: string | HTMLImageElement, tag: string) {
|
||||||
const tagData = this._instanceDataMap.get(tag);
|
const tagData = this._instanceDataMap.get(tag);
|
||||||
if (tagData) {
|
if (tagData) {
|
||||||
tagData._metallicRoughnessTextureSrc = src;
|
if (typeof src === 'string') {
|
||||||
|
tagData._metallicRoughnessTextureSrc = src;
|
||||||
|
tagData._metallicRoughnessTextureImage = null;
|
||||||
|
}
|
||||||
|
if (src instanceof HTMLImageElement) {
|
||||||
|
tagData._metallicRoughnessTextureSrc = null;
|
||||||
|
tagData._metallicRoughnessTextureImage = src;
|
||||||
|
}
|
||||||
this._instanceDataMap.set(tag, tagData);
|
this._instanceDataMap.set(tag, tagData);
|
||||||
this._loadMetallicRoughnessTexture(tagData);
|
this._loadMetallicRoughnessTexture(tagData);
|
||||||
}
|
}
|
||||||
@ -182,9 +203,13 @@ export class GeoObjectHandler {
|
|||||||
tagData._changedBuffers[TEXCOORD_BUFFER] = true;
|
tagData._changedBuffers[TEXCOORD_BUFFER] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
tagData._colorTextureSrc = object.colorTexture;
|
tagData._colorTextureSrc = object.colorTextureSrc;
|
||||||
tagData._normalTextureSrc = object.normalTexture;
|
tagData._normalTextureSrc = object.normalTextureSrc;
|
||||||
tagData._metallicRoughnessTexture = object.metallicRoughnessTexture;
|
tagData._metallicRoughnessTexture = object.metallicRoughnessTextureSrc;
|
||||||
|
tagData._colorTextureImage = object.colorTextureImage;
|
||||||
|
tagData._normalTextureImage = object.normalTextureImage;
|
||||||
|
tagData._metallicRoughnessTextureImage = object.metallicRoughnessTextureImage;
|
||||||
|
|
||||||
|
|
||||||
this._loadColorTexture(tagData);
|
this._loadColorTexture(tagData);
|
||||||
this._loadNormalTexture(tagData);
|
this._loadNormalTexture(tagData);
|
||||||
@ -212,9 +237,12 @@ export class GeoObjectHandler {
|
|||||||
tagData._indicesArr = geoObject.indices;
|
tagData._indicesArr = geoObject.indices;
|
||||||
tagData._texCoordArr = geoObject.texCoords;
|
tagData._texCoordArr = geoObject.texCoords;
|
||||||
|
|
||||||
tagData._colorTextureSrc = geoObject.object3d.colorTexture;
|
tagData._colorTextureSrc = geoObject.object3d.colorTextureSrc;
|
||||||
tagData._normalTextureSrc = geoObject.object3d.normalTexture;
|
tagData._normalTextureSrc = geoObject.object3d.normalTextureSrc;
|
||||||
tagData._metallicRoughnessTextureSrc = geoObject.object3d.metallicRoughnessTexture;
|
tagData._metallicRoughnessTextureSrc = geoObject.object3d.metallicRoughnessTextureSrc;
|
||||||
|
tagData._colorTextureImage = geoObject.object3d.colorTextureImage;
|
||||||
|
tagData._normalTextureImage = geoObject.object3d.normalTextureImage;
|
||||||
|
tagData._metallicRoughnessTextureImage = geoObject.object3d.metallicRoughnessTextureImage;
|
||||||
|
|
||||||
tagData.setMaterialParams(
|
tagData.setMaterialParams(
|
||||||
geoObject.object3d.ambient,
|
geoObject.object3d.ambient,
|
||||||
@ -489,23 +517,50 @@ export class GeoObjectHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _loadColorTexture(tagData: InstanceData) {
|
async _loadColorTexture(tagData: InstanceData) {
|
||||||
if (this._renderer && tagData._colorTextureSrc) {
|
if (!this._renderer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tagData._colorTextureSrc) {
|
||||||
const image = await loadImage(tagData._colorTextureSrc);
|
const image = await loadImage(tagData._colorTextureSrc);
|
||||||
tagData.createColorTexture(image);
|
tagData.createColorTexture(image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tagData._colorTextureImage) {
|
||||||
|
await tagData._colorTextureImage.decode();
|
||||||
|
tagData.createColorTexture(tagData._colorTextureImage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadNormalTexture(tagData: InstanceData) {
|
async _loadNormalTexture(tagData: InstanceData) {
|
||||||
if (this._renderer && tagData._normalTextureSrc) {
|
if (!this._renderer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tagData._normalTextureSrc) {
|
||||||
const image = await loadImage(tagData._normalTextureSrc);
|
const image = await loadImage(tagData._normalTextureSrc);
|
||||||
tagData.createNormalTexture(image);
|
tagData.createNormalTexture(image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tagData._normalTextureImage) {
|
||||||
|
await tagData._normalTextureImage.decode();
|
||||||
|
tagData.createNormalTexture(tagData._normalTextureImage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadMetallicRoughnessTexture(tagData: InstanceData) {
|
async _loadMetallicRoughnessTexture(tagData: InstanceData) {
|
||||||
if (this._renderer && tagData._metallicRoughnessTextureSrc) {
|
if (!this._renderer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tagData._metallicRoughnessTextureSrc) {
|
||||||
const image = await loadImage(tagData._metallicRoughnessTextureSrc);
|
const image = await loadImage(tagData._metallicRoughnessTextureSrc);
|
||||||
tagData.createMetallicRoughnessTexture(image);
|
tagData.createMetallicRoughnessTexture(image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tagData._metallicRoughnessTextureImage) {
|
||||||
|
await tagData._metallicRoughnessTextureImage.decode();
|
||||||
|
tagData.createMetallicRoughnessTexture(tagData._metallicRoughnessTextureImage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,6 +46,9 @@ export class InstanceData {
|
|||||||
public _colorTextureSrc: string | null;
|
public _colorTextureSrc: string | null;
|
||||||
public _normalTextureSrc: string | null;
|
public _normalTextureSrc: string | null;
|
||||||
public _metallicRoughnessTextureSrc: string | null;
|
public _metallicRoughnessTextureSrc: string | null;
|
||||||
|
public _colorTextureImage: HTMLImageElement | null;
|
||||||
|
public _normalTextureImage: HTMLImageElement | null;
|
||||||
|
public _metallicRoughnessTextureImage: HTMLImageElement | null;
|
||||||
|
|
||||||
public _objectSrc?: string;
|
public _objectSrc?: string;
|
||||||
|
|
||||||
@ -99,12 +102,15 @@ export class InstanceData {
|
|||||||
|
|
||||||
this._colorTexture = null;
|
this._colorTexture = null;
|
||||||
this._colorTextureSrc = null;
|
this._colorTextureSrc = null;
|
||||||
|
this._colorTextureImage = null;
|
||||||
|
|
||||||
this._normalTexture = null;
|
this._normalTexture = null;
|
||||||
this._normalTextureSrc = null;
|
this._normalTextureSrc = null;
|
||||||
|
this._normalTextureImage = null;
|
||||||
|
|
||||||
this._metallicRoughnessTexture = null;
|
this._metallicRoughnessTexture = null;
|
||||||
this._metallicRoughnessTextureSrc = null;
|
this._metallicRoughnessTextureSrc = null;
|
||||||
|
this._metallicRoughnessTextureImage = null;
|
||||||
|
|
||||||
this._sizeArr = [];
|
this._sizeArr = [];
|
||||||
this._translateArr = [];
|
this._translateArr = [];
|
||||||
@ -413,7 +419,6 @@ export class InstanceData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._sizeArr = makeArrayTyped(this._sizeArr);
|
this._sizeArr = makeArrayTyped(this._sizeArr);
|
||||||
|
|
||||||
h.setStreamArrayBuffer(this._sizeBuffer, this._sizeArr as Float32Array);
|
h.setStreamArrayBuffer(this._sizeBuffer, this._sizeArr as Float32Array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -86,6 +86,7 @@ import {
|
|||||||
} from './terrain/index';
|
} from './terrain/index';
|
||||||
|
|
||||||
import {MoveAxisEntity} from "./control/geoObjectEditor/MoveAxisEntity";
|
import {MoveAxisEntity} from "./control/geoObjectEditor/MoveAxisEntity";
|
||||||
|
import { Gltf } from './utils/gltf/gltfParser';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
bv,
|
bv,
|
||||||
@ -142,6 +143,6 @@ export {
|
|||||||
EarthQuadTreeStrategy,
|
EarthQuadTreeStrategy,
|
||||||
Wgs84QuadTreeStrategy,
|
Wgs84QuadTreeStrategy,
|
||||||
Object3d,
|
Object3d,
|
||||||
|
Gltf,
|
||||||
MoveAxisEntity,
|
MoveAxisEntity,
|
||||||
};
|
};
|
||||||
87
src/utils/gltf/glbParser.ts
Normal file
87
src/utils/gltf/glbParser.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { GltfData, GltfMetadata } from "./types";
|
||||||
|
|
||||||
|
interface GlbChunk {
|
||||||
|
length: number;
|
||||||
|
type: ChunkType;
|
||||||
|
chunkData: ArrayBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ChunkType {
|
||||||
|
JSON,
|
||||||
|
BIN,
|
||||||
|
INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Glb {
|
||||||
|
public static async load(src: string): Promise<GltfData> {
|
||||||
|
const response = await fetch(src);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Unable to load '${src}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
|
||||||
|
const dv = new DataView(buffer);
|
||||||
|
const magic = dv.getUint32(0, true);
|
||||||
|
|
||||||
|
if (magic !== 0x46546c67) {
|
||||||
|
throw new Error("Not a valid GLB");
|
||||||
|
}
|
||||||
|
const version = dv.getUint32(4, true);
|
||||||
|
const chunks = this.getChunks(dv);
|
||||||
|
return this.parseChunks(chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getChunks(dv: DataView): GlbChunk[] {
|
||||||
|
const chunks: GlbChunk[] = [];
|
||||||
|
let currentOffset = 12; // skip magic, version and total length
|
||||||
|
do {
|
||||||
|
const chunk = this.getChunk(dv, currentOffset);
|
||||||
|
currentOffset += 8 + chunk.length;
|
||||||
|
chunks.push(chunk);
|
||||||
|
} while (currentOffset < dv.byteLength);
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getChunk(dv: DataView, offset: number): GlbChunk {
|
||||||
|
const length = dv.getUint32(offset, true);
|
||||||
|
const type = this.getChunkType(dv.getUint32(offset + 4, true));
|
||||||
|
const chunkData = dv.buffer.slice(offset + 8, offset + 8 + length);
|
||||||
|
return { length, type, chunkData };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getChunkType(type: number): ChunkType {
|
||||||
|
switch (type) {
|
||||||
|
case 0x4e4f534a:
|
||||||
|
return ChunkType.JSON;
|
||||||
|
case 0x004e4942:
|
||||||
|
return ChunkType.BIN;
|
||||||
|
default:
|
||||||
|
return ChunkType.INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parseChunks(chunks: GlbChunk[]): GltfData {
|
||||||
|
const result = {
|
||||||
|
gltf: null as unknown as GltfMetadata,
|
||||||
|
bin: [] as ArrayBuffer[],
|
||||||
|
};
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
switch (chunk.type) {
|
||||||
|
case ChunkType.JSON:
|
||||||
|
result.gltf = this.parseJsonChunk(chunk);
|
||||||
|
break;
|
||||||
|
case ChunkType.BIN:
|
||||||
|
result.bin.push(chunk.chunkData);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parseJsonChunk(chunk: GlbChunk): GltfMetadata {
|
||||||
|
return JSON.parse(new TextDecoder().decode(chunk.chunkData));
|
||||||
|
}
|
||||||
|
}
|
||||||
442
src/utils/gltf/gltfParser.ts
Normal file
442
src/utils/gltf/gltfParser.ts
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
import { Entity } from "../../entity";
|
||||||
|
import { Vec3 } from "../../math/Vec3";
|
||||||
|
import { Object3d } from "../../Object3d";
|
||||||
|
import { Glb } from "./glbParser";
|
||||||
|
import {
|
||||||
|
Accessor,
|
||||||
|
AccessorComponentType,
|
||||||
|
AccessorDataType,
|
||||||
|
GltfData,
|
||||||
|
TextureImage,
|
||||||
|
Material,
|
||||||
|
Mesh,
|
||||||
|
Primitive,
|
||||||
|
PrimitiveMode,
|
||||||
|
Texture,
|
||||||
|
MimeType,
|
||||||
|
GltfNode,
|
||||||
|
GltfMesh,
|
||||||
|
GltfPrimitive
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export class Gltf {
|
||||||
|
private static dracoDecoderModule: any = null;
|
||||||
|
public static connectDracoDecoderModule(decoder: any): void {
|
||||||
|
this.dracoDecoderModule = decoder;
|
||||||
|
}
|
||||||
|
public static async loadGlb(url: string) {
|
||||||
|
const data = await Glb.load(url);
|
||||||
|
if (this.dracoDecoderModule !== null) {
|
||||||
|
await this.dracoDecoderModule.ready;
|
||||||
|
}
|
||||||
|
console.log("load glb", data);
|
||||||
|
return new Gltf(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private materials: Material[] = [];
|
||||||
|
private images: TextureImage[] = [];
|
||||||
|
public meshes: Mesh[] = [];
|
||||||
|
|
||||||
|
constructor(private gltf: GltfData) {
|
||||||
|
if (
|
||||||
|
gltf.gltf.extensionsRequired?.includes("KHR_draco_mesh_compression") &&
|
||||||
|
Gltf.dracoDecoderModule === null
|
||||||
|
) {
|
||||||
|
throw new Error("Unable to import GLTF. Draco decoder module is not connected");
|
||||||
|
}
|
||||||
|
this.initImages();
|
||||||
|
this.initMaterials();
|
||||||
|
this.initMeshes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getObjects3d(): Object3d[] {
|
||||||
|
return this.meshes
|
||||||
|
.map((mesh) => mesh.primitives.map((primitive) => Gltf.toObject3d(primitive)))
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
public toEntities(): Entity[] {
|
||||||
|
const result: Entity[] = [];
|
||||||
|
for (const scene of this.gltf.gltf.scenes) {
|
||||||
|
if (scene === undefined || scene.nodes === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
for (const node of scene.nodes) {
|
||||||
|
const nodeData = this.gltf.gltf.nodes[node];
|
||||||
|
if (nodeData.mesh === undefined && nodeData.children === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.push(this.nodeToEntity(nodeData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private nodeToEntity(node: GltfNode): Entity {
|
||||||
|
const entity = new Entity({
|
||||||
|
name: `node_${node.name}`,
|
||||||
|
cartesian: new Vec3(0, 0, 0),
|
||||||
|
relativePosition: true,
|
||||||
|
});
|
||||||
|
let meshEntity: Entity | null = null;
|
||||||
|
if (node.translation !== undefined) {
|
||||||
|
entity.relativePosition = true;
|
||||||
|
entity.setCartesian(node.translation[0], node.translation[1], node.translation[2]);
|
||||||
|
}
|
||||||
|
if (node.rotation !== undefined) {
|
||||||
|
// TODO: implement rotation by quaternion
|
||||||
|
}
|
||||||
|
if (node.matrix !== undefined) {
|
||||||
|
// TODO: implement matrix apply
|
||||||
|
}
|
||||||
|
if (node.scale !== undefined) {
|
||||||
|
entity.setScale3v(new Vec3(node.scale[0], node.scale[1], node.scale[2]));
|
||||||
|
}
|
||||||
|
if (node.mesh !== undefined) {
|
||||||
|
meshEntity = this.meshToEntity(this.meshes[node.mesh]);
|
||||||
|
entity.appendChild(meshEntity);
|
||||||
|
}
|
||||||
|
if (node.children !== undefined) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
const childEntity = this.nodeToEntity(this.gltf.gltf.nodes[child]);
|
||||||
|
if (meshEntity) {
|
||||||
|
meshEntity.appendChild(childEntity);
|
||||||
|
} else {
|
||||||
|
entity.appendChild(childEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public meshToEntity(mesh: Mesh): Entity {
|
||||||
|
const entity = new Entity({
|
||||||
|
name: mesh.name,
|
||||||
|
cartesian: new Vec3(0, 0, 0),
|
||||||
|
relativePosition: true,
|
||||||
|
independentPicking: true
|
||||||
|
});
|
||||||
|
mesh.primitives.map((primitive) => {
|
||||||
|
entity.appendChild(
|
||||||
|
new Entity({
|
||||||
|
name: primitive.name,
|
||||||
|
relativePosition: true,
|
||||||
|
geoObject: {
|
||||||
|
object3d: primitive.object3d,
|
||||||
|
tag: primitive.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private initImages() {
|
||||||
|
if (!this.gltf.gltf.images) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const image of this.gltf.gltf.images) {
|
||||||
|
this.images.push({
|
||||||
|
src: image.uri,
|
||||||
|
element: this.getImage(image.mimeType, image.bufferView),
|
||||||
|
mimeType: image.mimeType,
|
||||||
|
name: image.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getImage(mimeType?: MimeType, bufferView?: number): HTMLImageElement | undefined {
|
||||||
|
if (bufferView && mimeType) {
|
||||||
|
const view = this.gltf.gltf.bufferViews[bufferView];
|
||||||
|
const url = URL.createObjectURL(
|
||||||
|
new Blob(
|
||||||
|
[
|
||||||
|
this.gltf.bin[view.buffer].slice(
|
||||||
|
view.byteOffset,
|
||||||
|
view.byteOffset + view.byteLength
|
||||||
|
)
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: mimeType
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const img = new Image();
|
||||||
|
img.src = url;
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initMaterials() {
|
||||||
|
if (!this.gltf.gltf.materials) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const material of this.gltf.gltf.materials) {
|
||||||
|
const mat: Material = {
|
||||||
|
name: material.name,
|
||||||
|
emissiveFactor: material.emissiveFactor,
|
||||||
|
alphaMode: material.alphaMode,
|
||||||
|
alphaCutoff: material.alphaCutoff,
|
||||||
|
doubleSided: material.doubleSided
|
||||||
|
};
|
||||||
|
if (material.pbrMetallicRoughness) {
|
||||||
|
if (material.pbrMetallicRoughness.baseColorFactor) {
|
||||||
|
mat.baseColorFactor = material.pbrMetallicRoughness.baseColorFactor;
|
||||||
|
}
|
||||||
|
if (material.pbrMetallicRoughness.baseColorTexture) {
|
||||||
|
const source =
|
||||||
|
this.gltf.gltf.textures[
|
||||||
|
material.pbrMetallicRoughness.baseColorTexture.index
|
||||||
|
].source;
|
||||||
|
if (source !== undefined) {
|
||||||
|
mat.baseColorTexture = {
|
||||||
|
image: this.images[source],
|
||||||
|
texCoord: material.pbrMetallicRoughness.baseColorTexture.texCoord
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (material.pbrMetallicRoughness.metallicRoughnessTexture) {
|
||||||
|
const source =
|
||||||
|
this.gltf.gltf.textures[
|
||||||
|
material.pbrMetallicRoughness.metallicRoughnessTexture.index
|
||||||
|
].source;
|
||||||
|
if (source !== undefined) {
|
||||||
|
mat.metallicRoughnessTexture = {
|
||||||
|
image: this.images[source],
|
||||||
|
texCoord:
|
||||||
|
material.pbrMetallicRoughness.metallicRoughnessTexture.texCoord
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (material.normalTexture) {
|
||||||
|
const source = this.gltf.gltf.textures[material.normalTexture.index].source;
|
||||||
|
if (source !== undefined) {
|
||||||
|
mat.normalTexture = {
|
||||||
|
image: this.images[source],
|
||||||
|
texCoord: material.normalTexture.texCoord,
|
||||||
|
scale: material.normalTexture.scale
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (material.occlusionTexture) {
|
||||||
|
const source = this.gltf.gltf.textures[material.occlusionTexture.index].source;
|
||||||
|
if (source !== undefined) {
|
||||||
|
mat.occlusionTexture = {
|
||||||
|
image: this.images[source],
|
||||||
|
texCoord: material.occlusionTexture.texCoord,
|
||||||
|
strength: material.occlusionTexture.strength
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (material.emissiveTexture) {
|
||||||
|
const source = this.gltf.gltf.textures[material.emissiveTexture.index].source;
|
||||||
|
if (source !== undefined) {
|
||||||
|
mat.emissiveTexture = {
|
||||||
|
image: this.images[source],
|
||||||
|
texCoord: material.emissiveTexture.texCoord
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.materials.push(mat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initMeshes() {
|
||||||
|
this.meshes = [];
|
||||||
|
for (const meshData of this.gltf.gltf.meshes) {
|
||||||
|
const mesh: Mesh = {
|
||||||
|
name: meshData.name,
|
||||||
|
primitives: []
|
||||||
|
};
|
||||||
|
for (let i = 0; i < meshData.primitives.length; i++) {
|
||||||
|
mesh.primitives.push(this.buildPrimitive(meshData, meshData.primitives[i], i));
|
||||||
|
}
|
||||||
|
this.meshes.push(mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildPrimitive(
|
||||||
|
meshData: GltfMesh,
|
||||||
|
primitiveData: GltfPrimitive,
|
||||||
|
index: number = 0
|
||||||
|
): Primitive {
|
||||||
|
let primitive: Primitive | null = null;
|
||||||
|
const material = this.materials[primitiveData.material || 0];
|
||||||
|
const texcoord = material.baseColorTexture?.texCoord
|
||||||
|
? `TEXCOORD_${material.baseColorTexture.texCoord}`
|
||||||
|
: `TEXCOORD_0`;
|
||||||
|
if (primitiveData.extensions?.KHR_draco_mesh_compression) {
|
||||||
|
const dracoExt = primitiveData.extensions.KHR_draco_mesh_compression;
|
||||||
|
const bufferView = this.gltf.gltf.bufferViews[dracoExt.bufferView];
|
||||||
|
const bvOffset = bufferView.byteOffset || 0;
|
||||||
|
const draco = Gltf.dracoDecoderModule;
|
||||||
|
const decoder = new draco.Decoder();
|
||||||
|
const decoderBuffer = new draco.DecoderBuffer();
|
||||||
|
decoderBuffer.Init(
|
||||||
|
new Uint8Array(
|
||||||
|
this.gltf.bin[bufferView.buffer].slice(
|
||||||
|
bvOffset,
|
||||||
|
bvOffset + bufferView.byteLength
|
||||||
|
)
|
||||||
|
),
|
||||||
|
bufferView.byteLength
|
||||||
|
);
|
||||||
|
|
||||||
|
const geometryType = decoder.GetEncodedGeometryType(decoderBuffer);
|
||||||
|
if (geometryType !== draco.TRIANGULAR_MESH) {
|
||||||
|
throw new Error("Draco compressed data is not a mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
const mesh = new draco.Mesh();
|
||||||
|
const status = decoder.DecodeBufferToMesh(decoderBuffer, mesh);
|
||||||
|
if (!status.ok() || mesh.ptr === 0) {
|
||||||
|
throw new Error("Failed to decode Draco mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
const numFaces = mesh.num_faces();
|
||||||
|
const numIndices = numFaces * 3;
|
||||||
|
const indices = new Uint32Array(numIndices);
|
||||||
|
const ia = new draco.DracoInt32Array();
|
||||||
|
for (let i = 0; i < numFaces; i++) {
|
||||||
|
decoder.GetFaceFromMesh(mesh, i, ia);
|
||||||
|
indices[i * 3] = ia.GetValue(0);
|
||||||
|
indices[i * 3 + 1] = ia.GetValue(1);
|
||||||
|
indices[i * 3 + 2] = ia.GetValue(2);
|
||||||
|
}
|
||||||
|
draco.destroy(ia);
|
||||||
|
|
||||||
|
const attributes: { [name: string]: Float32Array } = {};
|
||||||
|
for (const gltfAttrName in dracoExt.attributes) {
|
||||||
|
const attrId = dracoExt.attributes[gltfAttrName];
|
||||||
|
const dracoAttr = decoder.GetAttributeByUniqueId(mesh, attrId);
|
||||||
|
const numPoints = mesh.num_points();
|
||||||
|
const numComponents = dracoAttr.num_components(); // 3 for POSITION, 2 for UVs, etc.
|
||||||
|
|
||||||
|
const attrArray = new draco.DracoFloat32Array();
|
||||||
|
decoder.GetAttributeFloatForAllPoints(mesh, dracoAttr, attrArray);
|
||||||
|
|
||||||
|
const typedArray = new Float32Array(numPoints * numComponents);
|
||||||
|
for (let i = 0; i < typedArray.length; i++) {
|
||||||
|
typedArray[i] = attrArray.GetValue(i);
|
||||||
|
}
|
||||||
|
draco.destroy(attrArray);
|
||||||
|
|
||||||
|
attributes[gltfAttrName] = typedArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
draco.destroy(mesh);
|
||||||
|
draco.destroy(decoderBuffer);
|
||||||
|
draco.destroy(decoder);
|
||||||
|
|
||||||
|
primitive = {
|
||||||
|
name: `${meshData.name}_${material.name}_${index}`,
|
||||||
|
vertices: attributes.POSITION,
|
||||||
|
indices: indices,
|
||||||
|
mode: primitiveData.mode ? primitiveData.mode : PrimitiveMode.triangles,
|
||||||
|
material: this.materials[primitiveData.material || 0] || undefined,
|
||||||
|
normals: attributes.NORMAL,
|
||||||
|
texCoords: undefined
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const texcoordAccessorKey = texcoord ? primitiveData.attributes[texcoord] : undefined;
|
||||||
|
const texcoordAccessor = texcoordAccessorKey
|
||||||
|
? this.gltf.gltf.accessors[texcoordAccessorKey]
|
||||||
|
: undefined;
|
||||||
|
primitive = {
|
||||||
|
name: `${meshData.name}_${material.name}_${index}`,
|
||||||
|
indices: primitiveData.indices
|
||||||
|
? Gltf.access(this.gltf.gltf.accessors[primitiveData.indices], this.gltf)
|
||||||
|
: undefined,
|
||||||
|
mode: primitiveData.mode ? primitiveData.mode : PrimitiveMode.triangles,
|
||||||
|
material: this.materials[primitiveData.material || 0] || undefined,
|
||||||
|
vertices: Gltf.access(
|
||||||
|
this.gltf.gltf.accessors[primitiveData.attributes.POSITION],
|
||||||
|
this.gltf
|
||||||
|
),
|
||||||
|
normals: Gltf.access(
|
||||||
|
this.gltf.gltf.accessors[primitiveData.attributes.NORMAL],
|
||||||
|
this.gltf
|
||||||
|
),
|
||||||
|
texCoords: texcoordAccessor ? Gltf.access(texcoordAccessor, this.gltf) : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (primitive === null) {
|
||||||
|
throw new Error("Unable to build primitive");
|
||||||
|
}
|
||||||
|
primitive.object3d = Gltf.toObject3d(primitive);
|
||||||
|
return primitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static toObject3d(primitive: Primitive): Object3d {
|
||||||
|
console.log('building object3d', primitive);
|
||||||
|
return new Object3d({
|
||||||
|
name: primitive.name,
|
||||||
|
vertices: Array.from(primitive.vertices as Float32Array),
|
||||||
|
normals: Array.from(primitive.normals as Float32Array),
|
||||||
|
texCoords: primitive.texCoords
|
||||||
|
? Array.from(primitive.texCoords as Float32Array)
|
||||||
|
: undefined,
|
||||||
|
indices: Array.from(primitive.indices as Uint8Array),
|
||||||
|
normalTextureImage: primitive.material?.normalTexture?.image.element,
|
||||||
|
normalTextureSrc: primitive.material?.normalTexture?.image.src,
|
||||||
|
colorTextureImage: primitive.material?.baseColorTexture?.image.element,
|
||||||
|
colorTextureSrc: primitive.material?.baseColorTexture?.image.src,
|
||||||
|
metallicRoughnessTextureImage: primitive.material?.occlusionTexture?.image.element,
|
||||||
|
metallicRoughnessTextureSrc: primitive.material?.occlusionTexture?.image.src,
|
||||||
|
color: primitive.material?.baseColorFactor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static access(accessor: Accessor, gltf: GltfData): ArrayBufferLike {
|
||||||
|
const bufferView = gltf.gltf.bufferViews[accessor.bufferView];
|
||||||
|
const arrbuff = gltf.bin[bufferView.buffer];
|
||||||
|
const offset = bufferView.byteOffset || 0;
|
||||||
|
const dv = arrbuff.slice(offset, offset + bufferView.byteLength);
|
||||||
|
switch (accessor.type) {
|
||||||
|
case AccessorDataType.scalar:
|
||||||
|
return this.getTensor(dv, accessor, 1);
|
||||||
|
case AccessorDataType.vec2:
|
||||||
|
return this.getTensor(dv, accessor, 2);
|
||||||
|
case AccessorDataType.vec3:
|
||||||
|
return this.getTensor(dv, accessor, 3);
|
||||||
|
case AccessorDataType.vec4:
|
||||||
|
case AccessorDataType.mat2:
|
||||||
|
return this.getTensor(dv, accessor, 4);
|
||||||
|
case AccessorDataType.mat3:
|
||||||
|
return this.getTensor(dv, accessor, 9);
|
||||||
|
case AccessorDataType.mat4:
|
||||||
|
return this.getTensor(dv, accessor, 16);
|
||||||
|
default:
|
||||||
|
throw new Error("Unknown accessor type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getTensor(
|
||||||
|
buffer: ArrayBuffer,
|
||||||
|
accessor: Accessor,
|
||||||
|
numOfComponents: number
|
||||||
|
): ArrayBufferLike {
|
||||||
|
if (accessor.componentType === AccessorComponentType.ushort) {
|
||||||
|
return new Uint16Array(buffer, 0, accessor.count * numOfComponents);
|
||||||
|
}
|
||||||
|
if (accessor.componentType === AccessorComponentType.short) {
|
||||||
|
return new Int16Array(buffer, 0, accessor.count * numOfComponents);
|
||||||
|
}
|
||||||
|
if (accessor.componentType === AccessorComponentType.uint) {
|
||||||
|
return new Uint32Array(buffer, 0, accessor.count * numOfComponents);
|
||||||
|
}
|
||||||
|
if (accessor.componentType === AccessorComponentType.float) {
|
||||||
|
return new Float32Array(buffer, 0, accessor.count * numOfComponents);
|
||||||
|
}
|
||||||
|
if (accessor.componentType === AccessorComponentType.ubyte) {
|
||||||
|
return new Uint8Array(buffer, 0, accessor.count * numOfComponents);
|
||||||
|
}
|
||||||
|
if (accessor.componentType === AccessorComponentType.byte) {
|
||||||
|
return new Int8Array(buffer, 0, accessor.count * numOfComponents);
|
||||||
|
}
|
||||||
|
throw new Error("Unknown component type");
|
||||||
|
}
|
||||||
|
}
|
||||||
216
src/utils/gltf/types.ts
Normal file
216
src/utils/gltf/types.ts
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import { Object3d } from "../../Object3d";
|
||||||
|
|
||||||
|
export interface GltfData {
|
||||||
|
bin: ArrayBuffer[];
|
||||||
|
gltf: GltfMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GltfMetadata {
|
||||||
|
accessors: Accessor[];
|
||||||
|
bufferViews: BufferView[];
|
||||||
|
meshes: GltfMesh[];
|
||||||
|
materials: {
|
||||||
|
name: string;
|
||||||
|
pbrMetallicRoughness?: {
|
||||||
|
baseColorFactor?: number[];
|
||||||
|
metallicFactor?: number;
|
||||||
|
roughnessFactor?: number;
|
||||||
|
baseColorTexture?: {
|
||||||
|
index: number;
|
||||||
|
texCoord?: number;
|
||||||
|
};
|
||||||
|
metallicRoughnessTexture?: {
|
||||||
|
index: number;
|
||||||
|
texCoord?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
normalTexture?: {
|
||||||
|
index: number;
|
||||||
|
texCoord?: number;
|
||||||
|
scale?: number;
|
||||||
|
};
|
||||||
|
occlusionTexture?: {
|
||||||
|
index: number;
|
||||||
|
texCoord?: number;
|
||||||
|
strength?: number;
|
||||||
|
};
|
||||||
|
emissiveTexture?: {
|
||||||
|
index: number;
|
||||||
|
texCoord?: number;
|
||||||
|
};
|
||||||
|
emissiveFactor?: number[];
|
||||||
|
alphaMode?: AlphaMode;
|
||||||
|
alphaCutoff?: number;
|
||||||
|
doubleSided?: boolean;
|
||||||
|
}[];
|
||||||
|
textures: {
|
||||||
|
sampler?: number;
|
||||||
|
source?: number;
|
||||||
|
name?: string;
|
||||||
|
}[];
|
||||||
|
images?: {
|
||||||
|
uri?: string;
|
||||||
|
mimeType?: MimeType;
|
||||||
|
bufferView?: number;
|
||||||
|
name?: string;
|
||||||
|
}[];
|
||||||
|
scene: number;
|
||||||
|
scenes: {
|
||||||
|
nodes?: number[];
|
||||||
|
name?: string;
|
||||||
|
}[];
|
||||||
|
nodes: GltfNode[];
|
||||||
|
extensionsRequired?: string[];
|
||||||
|
extensionsUsed?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GltfMesh {
|
||||||
|
name: string;
|
||||||
|
primitives: GltfPrimitive[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GltfPrimitive {
|
||||||
|
indices?: number;
|
||||||
|
material?: number;
|
||||||
|
mode?: PrimitiveMode;
|
||||||
|
attributes: {
|
||||||
|
POSITION: number;
|
||||||
|
NORMAL: number;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
extensions?: {
|
||||||
|
[key: string]: any;
|
||||||
|
KHR_draco_mesh_compression?: {
|
||||||
|
bufferView: number;
|
||||||
|
attributes: {
|
||||||
|
POSITION: number;
|
||||||
|
NORMAL: number;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GltfNode {
|
||||||
|
camera?: number;
|
||||||
|
children?: number[];
|
||||||
|
skin?: number;
|
||||||
|
matrix?: number[];
|
||||||
|
mesh?: number;
|
||||||
|
rotation?: number[];
|
||||||
|
scale?: number[];
|
||||||
|
translation?: number[];
|
||||||
|
weights?: number[];
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MimeType {
|
||||||
|
JPEG = "image/jpeg",
|
||||||
|
PNG = "image/png"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Accessor {
|
||||||
|
bufferView: number;
|
||||||
|
componentType: AccessorComponentType;
|
||||||
|
count: number;
|
||||||
|
type: AccessorDataType;
|
||||||
|
max: number[];
|
||||||
|
min: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BufferView {
|
||||||
|
buffer: number;
|
||||||
|
byteLength: number;
|
||||||
|
byteOffset: number;
|
||||||
|
target?: BufferViewTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BufferViewTarget {
|
||||||
|
ARRAY_BUFFER = 34962,
|
||||||
|
ELEMENT_ARRAY_BUFFER = 34963
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AccessorComponentType {
|
||||||
|
byte = 5120,
|
||||||
|
ubyte = 5121,
|
||||||
|
short = 5122,
|
||||||
|
ushort = 5123,
|
||||||
|
uint = 5125,
|
||||||
|
float = 5126
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AccessorDataType {
|
||||||
|
scalar = "SCALAR",
|
||||||
|
vec2 = "VEC2",
|
||||||
|
vec3 = "VEC3",
|
||||||
|
vec4 = "VEC4",
|
||||||
|
mat2 = "MAT2",
|
||||||
|
mat3 = "MAT3",
|
||||||
|
mat4 = "MAT4"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Mesh {
|
||||||
|
name: string;
|
||||||
|
primitives: Primitive[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Primitive {
|
||||||
|
name: string;
|
||||||
|
indices?: ArrayBufferLike;
|
||||||
|
vertices: ArrayBufferLike;
|
||||||
|
normals: ArrayBufferLike;
|
||||||
|
texCoords?: ArrayBufferLike;
|
||||||
|
material?: Material;
|
||||||
|
mode: PrimitiveMode;
|
||||||
|
object3d?: Object3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Material {
|
||||||
|
name: string;
|
||||||
|
baseColorFactor?: number[];
|
||||||
|
baseColorTexture?: Texture;
|
||||||
|
metallicRoughnessTexture?: Texture;
|
||||||
|
normalTexture?: NormalTexture;
|
||||||
|
occlusionTexture?: OcclusionTexture;
|
||||||
|
emissiveTexture?: Texture;
|
||||||
|
emissiveFactor?: number[];
|
||||||
|
alphaMode?: AlphaMode;
|
||||||
|
alphaCutoff?: number;
|
||||||
|
doubleSided?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Texture {
|
||||||
|
image: TextureImage;
|
||||||
|
texCoord?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NormalTexture extends Texture {
|
||||||
|
scale?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OcclusionTexture extends Texture {
|
||||||
|
strength?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextureImage {
|
||||||
|
src?: string;
|
||||||
|
element?: HTMLImageElement;
|
||||||
|
mimeType?: MimeType;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AlphaMode {
|
||||||
|
OPAQUE = "OPAQUE",
|
||||||
|
MASK = "MASK",
|
||||||
|
BLEND = "BLEND"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PrimitiveMode {
|
||||||
|
points = 0,
|
||||||
|
lines = 1,
|
||||||
|
lineLoop = 2,
|
||||||
|
lineStrip = 3,
|
||||||
|
triangles = 4,
|
||||||
|
triangleStrip = 5,
|
||||||
|
triangleFan = 6
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user