From d0fc9cd61c9b1dd7b04be1e50209e67a9024a8bc Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Sun, 4 Nov 2018 11:47:49 -0800 Subject: [PATCH 1/5] Clean up code --- server/src/server.ts | 2 - server/tsconfig.json | 5 +- src/GLTF2.d.ts | 890 ++++++++++++++++++++++ src/dataUriTextDocumentContentProvider.ts | 224 +----- src/extension.ts | 67 +- src/gltfOutlineTreeDataProvider.ts | 20 +- src/gltfPreviewDocumentContentProvider.ts | 20 +- src/utilities.ts | 126 +++ src/validationProvider.ts | 13 +- tsconfig.json | 9 +- 10 files changed, 1121 insertions(+), 255 deletions(-) create mode 100644 src/GLTF2.d.ts create mode 100644 src/utilities.ts diff --git a/server/src/server.ts b/server/src/server.ts index b47ed62..283689d 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,5 +1,3 @@ -'use strict'; - import { IPCMessageReader, IPCMessageWriter, createConnection, IConnection, TextDocuments, TextDocument, Diagnostic, DiagnosticSeverity, InitializeResult, Position, Range, TextDocumentPositionParams, Hover, MarkedString, diff --git a/server/tsconfig.json b/server/tsconfig.json index 402a844..73d5620 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,14 +1,15 @@ { "compilerOptions": { + "alwaysStrict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitAny": false, "noImplicitReturns": true, - "target": "es6", + "target": "es2017", "module": "commonjs", "moduleResolution": "node", "sourceMap": true, - "lib" : [ "es2016" ], + "lib" : [ "es2017" ], "outDir": "." }, "exclude": [ diff --git a/src/GLTF2.d.ts b/src/GLTF2.d.ts new file mode 100644 index 0000000..bdaebc6 --- /dev/null +++ b/src/GLTF2.d.ts @@ -0,0 +1,890 @@ +// Type definitions for glTF 2.0 +// Project: https://github.com/KhronosGroup/glTF +// Definitions by: Gary Hsu +// Don McCurdy +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.4 + +export namespace GLTF2 { + /** + * The datatype of the components in the attribute + */ + // tslint:disable-next-line no-const-enum + const enum AccessorComponentType { + /** + * Byte + */ + BYTE = 5120, + /** + * Unsigned Byte + */ + UNSIGNED_BYTE = 5121, + /** + * Short + */ + SHORT = 5122, + /** + * Unsigned Short + */ + UNSIGNED_SHORT = 5123, + /** + * Unsigned Int + */ + UNSIGNED_INT = 5125, + /** + * Float + */ + FLOAT = 5126, + } + /** + * Specifies if the attirbute is a scalar, vector, or matrix + */ + // tslint:disable-next-line no-const-enum + const enum AccessorType { + /** + * Scalar + */ + SCALAR = "SCALAR", + /** + * Vector2 + */ + VEC2 = "VEC2", + /** + * Vector3 + */ + VEC3 = "VEC3", + /** + * Vector4 + */ + VEC4 = "VEC4", + /** + * Matrix2x2 + */ + MAT2 = "MAT2", + /** + * Matrix3x3 + */ + MAT3 = "MAT3", + /** + * Matrix4x4 + */ + MAT4 = "MAT4", + } + /** + * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates + */ + // tslint:disable-next-line no-const-enum + const enum AnimationChannelTargetPath { + /** + * Translation + */ + TRANSLATION = "translation", + /** + * Rotation + */ + ROTATION = "rotation", + /** + * Scale + */ + SCALE = "scale", + /** + * Weights + */ + WEIGHTS = "weights", + } + /** + * Interpolation algorithm + */ + // tslint:disable-next-line no-const-enum + const enum AnimationSamplerInterpolation { + /** + * The animated values are linearly interpolated between keyframes + */ + LINEAR = "LINEAR", + /** + * The animated values remain constant to the output of the first keyframe, until the next keyframe + */ + STEP = "STEP", + /** + * The animation's interpolation is computed using a cubic spline with specified tangents + */ + CUBICSPLINE = "CUBICSPLINE", + } + /** + * The target that the GPU buffer should be bound to + */ + // tslint:disable-next-line no-const-enum + const enum BufferViewTarget { + /** + * Buffer containing vertex attributes, such as vertex coordinates, texture coordinate data, or vertex color data + */ + ARRAY_BUFFER = 34962, + /** + * Buffer used for element indices + */ + ELEMENT_ARRAY_BUFFER = 34963, + } + /** + * A camera's projection. A node can reference a camera to apply a transform to place the camera in the scene + */ + // tslint:disable-next-line no-const-enum + const enum CameraType { + /** + * A perspective camera containing properties to create a perspective projection matrix + */ + PERSPECTIVE = "perspective", + /** + * An orthographic camera containing properties to create an orthographic projection matrix + */ + ORTHOGRAPHIC = "orthographic", + } + /** + * The mime-type of the image + */ + // tslint:disable-next-line no-const-enum + const enum ImageMimeType { + /** + * JPEG Mime-type + */ + JPEG = "image/jpeg", + /** + * PNG Mime-type + */ + PNG = "image/png", + } + /** + * The alpha rendering mode of the material + */ + // tslint:disable-next-line no-const-enum + const enum MaterialAlphaMode { + /** + * The alpha value is ignored and the rendered output is fully opaque + */ + OPAQUE = "OPAQUE", + /** + * The rendered output is either fully opaque or fully transparent depending on the alpha value and the specified + * alpha cutoff value + */ + MASK = "MASK", + /** + * The alpha value is used to composite the source and destination areas. The rendered output is combined with the + * background using the normal painting operation (i.e. the Porter and Duff over operator) + */ + BLEND = "BLEND", + } + /** + * The type of the primitives to render + */ + // tslint:disable-next-line no-const-enum + const enum MeshPrimitiveMode { + /** + * Points + */ + POINTS = 0, + /** + * Lines + */ + LINES = 1, + /** + * Line Loop + */ + LINE_LOOP = 2, + /** + * Line Strip + */ + LINE_STRIP = 3, + /** + * Triangles + */ + TRIANGLES = 4, + /** + * Triangle Strip + */ + TRIANGLE_STRIP = 5, + /** + * Triangle Fan + */ + TRIANGLE_FAN = 6, + } + /** + * Magnification filter. Valid values correspond to WebGL enums: 9728 (NEAREST) and 9729 (LINEAR) + */ + // tslint:disable-next-line no-const-enum + const enum TextureMagFilter { + /** + * Nearest + */ + NEAREST = 9728, + /** + * Linear + */ + LINEAR = 9729, + } + /** + * Minification filter. All valid values correspond to WebGL enums + */ + // tslint:disable-next-line no-const-enum + const enum TextureMinFilter { + /** + * Nearest + */ + NEAREST = 9728, + /** + * Linear + */ + LINEAR = 9729, + /** + * Nearest Mip-Map Nearest + */ + NEAREST_MIPMAP_NEAREST = 9984, + /** + * Linear Mipmap Nearest + */ + LINEAR_MIPMAP_NEAREST = 9985, + /** + * Nearest Mipmap Linear + */ + NEAREST_MIPMAP_LINEAR = 9986, + /** + * Linear Mipmap Linear + */ + LINEAR_MIPMAP_LINEAR = 9987, + } + /** + * S (U) wrapping mode. All valid values correspond to WebGL enums + */ + // tslint:disable-next-line no-const-enum + const enum TextureWrapMode { + /** + * Clamp to Edge + */ + CLAMP_TO_EDGE = 33071, + /** + * Mirrored Repeat + */ + MIRRORED_REPEAT = 33648, + /** + * Repeat + */ + REPEAT = 10497, + } + /** + * glTF Property + */ + interface Property { + /** + * Dictionary object with extension-specific objects + */ + extensions?: { + [key: string]: any; + }; + /** + * Application-Specific data + */ + extras?: any; + } + /** + * glTF Child of Root Property + */ + interface ChildRootProperty extends Property { + /** + * The user-defined name of this object + */ + name?: string; + } + /** + * Indices of those attributes that deviate from their initialization value + */ + interface AccessorSparseIndices extends Property { + /** + * The index of the bufferView with sparse indices. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target + */ + bufferView: number; + /** + * The offset relative to the start of the bufferView in bytes. Must be aligned + */ + byteOffset?: number; + /** + * The indices data type. Valid values correspond to WebGL enums: 5121 (UNSIGNED_BYTE), 5123 (UNSIGNED_SHORT), 5125 (UNSIGNED_INT) + */ + componentType: AccessorComponentType; + } + /** + * Array of size accessor.sparse.count times number of components storing the displaced accessor attributes pointed by accessor.sparse.indices + */ + interface AccessorSparseValues extends Property { + /** + * The index of the bufferView with sparse values. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target + */ + bufferView: number; + /** + * The offset relative to the start of the bufferView in bytes. Must be aligned + */ + byteOffset?: number; + } + /** + * Sparse storage of attributes that deviate from their initialization value + */ + interface AccessorSparse extends Property { + /** + * The number of attributes encoded in this sparse accessor + */ + count: number; + /** + * Index array of size count that points to those accessor attributes that deviate from their initialization value. + * Indices must strictly increase + */ + indices: AccessorSparseIndices; + /** + * Array of size count times number of components, storing the displaced accessor attributes pointed by indices. + * Substituted values must have the same componentType and number of components as the base accessor + */ + values: AccessorSparseValues; + } + /** + * A typed view into a bufferView. A bufferView contains raw binary data. An accessor provides a typed view into a + * bufferView or a subset of a bufferView similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer + */ + interface Accessor extends ChildRootProperty { + /** + * The index of the bufferview + */ + bufferView?: number; + /** + * The offset relative to the start of the bufferView in bytes + */ + byteOffset?: number; + /** + * The datatype of components in the attribute + */ + componentType: AccessorComponentType; + /** + * Specifies whether integer data values should be normalized + */ + normalized?: boolean; + /** + * The number of attributes referenced by this accessor + */ + count: number; + /** + * Specifies if the attribute is a scalar, vector, or matrix + */ + type: AccessorType; + /** + * Maximum value of each component in this attribute + */ + max?: number[]; + /** + * Minimum value of each component in this attribute + */ + min?: number[]; + /** + * Sparse storage of attributes that deviate from their initialization value + */ + sparse?: AccessorSparse; + } + /** + * Targets an animation's sampler at a node's property + */ + interface AnimationChannel extends Property { + /** + * The index of a sampler in this animation used to compute the value for the target + */ + sampler: number; + /** + * The index of the node and TRS property to target + */ + target: AnimationChannelTarget; + } + /** + * The index of the node and TRS property that an animation channel targets + */ + interface AnimationChannelTarget extends Property { + /** + * The index of the node to target + */ + node: number; + /** + * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates + */ + path: AnimationChannelTargetPath; + } + /** + * Combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target) + */ + interface AnimationSampler extends Property { + /** + * The index of an accessor containing keyframe input values, e.g., time + */ + input: number; + /** + * Interpolation algorithm + */ + interpolation?: AnimationSamplerInterpolation; + /** + * The index of an accessor, containing keyframe output values + */ + output: number; + } + /** + * A keyframe animation + */ + interface Animation extends ChildRootProperty { + /** + * An array of channels, each of which targets an animation's sampler at a node's property + */ + channels: AnimationChannel[]; + /** + * An array of samplers that combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target) + */ + samplers: AnimationSampler[]; + } + /** + * Metadata about the glTF asset + */ + interface Asset extends ChildRootProperty { + /** + * A copyright message suitable for display to credit the content creator + */ + copyright?: string; + /** + * Tool that generated this glTF model. Useful for debugging + */ + generator?: string; + /** + * The glTF version that this asset targets + */ + version: string; + /** + * The minimum glTF version that this asset targets + */ + minVersion?: string; + } + /** + * A buffer points to binary geometry, animation, or skins + */ + interface Buffer extends ChildRootProperty { + /** + * The uri of the buffer. Relative paths are relative to the .gltf file. Instead of referencing an external file, the uri can also be a data-uri + */ + uri?: string; + /** + * The length of the buffer in bytes + */ + byteLength: number; + } + /** + * A view into a buffer generally representing a subset of the buffer + */ + interface BufferView extends ChildRootProperty { + /** + * The index of the buffer + */ + buffer: number; + /** + * The offset into the buffer in bytes + */ + byteOffset?: number; + /** + * The lenth of the bufferView in bytes + */ + byteLength: number; + /** + * The stride, in bytes + */ + byteStride?: number; + /** + * The target that the GPU buffer should be bound to + */ + target?: BufferViewTarget; + } + /** + * An orthographic camera containing properties to create an orthographic projection matrix + */ + interface CameraOrthographic extends Property { + /** + * The floating-point horizontal magnification of the view. Must not be zero + */ + xmag: number; + /** + * The floating-point vertical magnification of the view. Must not be zero + */ + ymag: number; + /** + * The floating-point distance to the far clipping plane. zfar must be greater than znear + */ + zfar: number; + /** + * The floating-point distance to the near clipping plane + */ + znear: number; + } + /** + * A perspective camera containing properties to create a perspective projection matrix + */ + interface CameraPerspective extends Property { + /** + * The floating-point aspect ratio of the field of view + */ + aspectRatio?: number; + /** + * The floating-point vertical field of view in radians + */ + yfov: number; + /** + * The floating-point distance to the far clipping plane + */ + zfar?: number; + /** + * The floating-point distance to the near clipping plane + */ + znear: number; + } + /** + * A camera's projection. A node can reference a camera to apply a transform to place the camera in the scene + */ + interface Camera extends ChildRootProperty { + /** + * An orthographic camera containing properties to create an orthographic projection matrix + */ + orthographic?: CameraOrthographic; + /** + * A perspective camera containing properties to create a perspective projection matrix + */ + perspective?: CameraPerspective; + /** + * Specifies if the camera uses a perspective or orthographic projection + */ + type: CameraType; + } + /** + * Image data used to create a texture. Image can be referenced by URI or bufferView index. mimeType is required in the latter case + */ + interface Image extends ChildRootProperty { + /** + * The uri of the image. Relative paths are relative to the .gltf file. Instead of referencing an external file, the uri can also be a data-uri. The image format must be jpg or png + */ + uri?: string; + /** + * The image's MIME type + */ + mimeType?: ImageMimeType; + /** + * The index of the bufferView that contains the image. Use this instead of the image's uri property + */ + bufferView?: number; + } + /** + * Material Normal Texture Info + */ + interface MaterialNormalTextureInfo extends TextureInfo { + /** + * The scalar multiplier applied to each normal vector of the normal texture + */ + scale?: number; + } + /** + * Material Occlusion Texture Info + */ + interface MaterialOcclusionTextureInfo extends TextureInfo { + /** + * A scalar multiplier controlling the amount of occlusion applied + */ + strength?: number; + } + /** + * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology + */ + interface MaterialPbrMetallicRoughness { + /** + * The material's base color factor + */ + baseColorFactor?: number[]; + /** + * The base color texture + */ + baseColorTexture?: TextureInfo; + /** + * The metalness of the material + */ + metallicFactor?: number; + /** + * The roughness of the material + */ + roughnessFactor?: number; + /** + * The metallic-roughness texture + */ + metallicRoughnessTexture?: TextureInfo; + } + /** + * The material appearance of a primitive + */ + interface Material extends ChildRootProperty { + /** + * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based + * Rendering (PBR) methodology. When not specified, all the default values of pbrMetallicRoughness apply + */ + pbrMetallicRoughness?: MaterialPbrMetallicRoughness; + /** + * The normal map texture + */ + normalTexture?: MaterialNormalTextureInfo; + /** + * The occlusion map texture + */ + occlusionTexture?: MaterialOcclusionTextureInfo; + /** + * The emissive map texture + */ + emissiveTexture?: TextureInfo; + /** + * The RGB components of the emissive color of the material. These values are linear. If an emissiveTexture is specified, this value is multiplied with the texel values + */ + emissiveFactor?: number[]; + /** + * The alpha rendering mode of the material + */ + alphaMode?: MaterialAlphaMode; + /** + * The alpha cutoff value of the material + */ + alphaCutoff?: number; + /** + * Specifies whether the material is double sided + */ + doubleSided?: boolean; + } + /** + * Geometry to be rendered with the given material + */ + interface MeshPrimitive extends Property { + /** + * A dictionary object, where each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data + */ + attributes: { + [name: string]: number; + }; + /** + * The index of the accessor that contains the indices + */ + indices?: number; + /** + * The index of the material to apply to this primitive when rendering + */ + material?: number; + /** + * The type of primitives to render. All valid values correspond to WebGL enums + */ + mode?: MeshPrimitiveMode; + /** + * An array of Morph Targets, each Morph Target is a dictionary mapping attributes (only POSITION, NORMAL, and TANGENT supported) to their deviations in the Morph Target + */ + targets?: Array<{ + [name: string]: number; + }>; + } + /** + * A set of primitives to be rendered. A node can contain one mesh. A node's transform places the mesh in the scene + */ + interface Mesh extends ChildRootProperty { + /** + * An array of primitives, each defining geometry to be rendered with a material + */ + primitives: MeshPrimitive[]; + /** + * Array of weights to be applied to the Morph Targets + */ + weights?: number[]; + } + /** + * A node in the node hierarchy + */ + interface Node extends ChildRootProperty { + /** + * The index of the camera referenced by this node + */ + camera?: number; + /** + * The indices of this node's children + */ + children?: number[]; + /** + * The index of the skin referenced by this node + */ + skin?: number; + /** + * A floating-point 4x4 transformation matrix stored in column-major order + */ + matrix?: number[]; + /** + * The index of the mesh in this node + */ + mesh?: number; + /** + * The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar + */ + rotation?: number[]; + /** + * The node's non-uniform scale, given as the scaling factors along the x, y, and z axes + */ + scale?: number[]; + /** + * The node's translation along the x, y, and z axes + */ + translation?: number[]; + /** + * The weights of the instantiated Morph Target. Number of elements must match number of Morph Targets of used mesh + */ + weights?: number[]; + } + /** + * Texture sampler properties for filtering and wrapping modes + */ + interface Sampler extends ChildRootProperty { + /** + * Magnification filter. Valid values correspond to WebGL enums: 9728 (NEAREST) and 9729 (LINEAR) + */ + magFilter?: TextureMagFilter; + /** + * Minification filter. All valid values correspond to WebGL enums + */ + minFilter?: TextureMinFilter; + /** + * S (U) wrapping mode. All valid values correspond to WebGL enums + */ + wrapS?: TextureWrapMode; + /** + * T (V) wrapping mode. All valid values correspond to WebGL enums + */ + wrapT?: TextureWrapMode; + } + /** + * The root nodes of a scene + */ + interface Scene extends ChildRootProperty { + /** + * The indices of each root node + */ + nodes: number[]; + } + /** + * Joints and matrices defining a skin + */ + interface Skin extends ChildRootProperty { + /** + * The index of the accessor containing the floating-point 4x4 inverse-bind matrices. The default is that each + * matrix is a 4x4 identity matrix, which implies that inverse-bind matrices were pre-applied + */ + inverseBindMatrices?: number; + /** + * The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root + */ + skeleton?: number; + /** + * Indices of skeleton nodes, used as joints in this skin. The array length must be the same as the count property + * of the inverseBindMatrices accessor (when defined) + */ + joints: number[]; + } + /** + * A texture and its sampler + */ + interface Texture extends ChildRootProperty { + /** + * The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering + * should be used + */ + sampler?: number; + /** + * The index of the image used by this texture + */ + source: number; + } + /** + * Reference to a texture + */ + interface TextureInfo { + /** + * The index of the texture + */ + index: number; + /** + * The set index of texture's TEXCOORD attribute used for texture coordinate mapping + */ + texCoord?: number; + } + /** + * The root object for a glTF asset + */ + interface GLTF extends Property { + /** + * An array of accessors. An accessor is a typed view into a bufferView + */ + accessors?: Accessor[]; + /** + * An array of keyframe animations + */ + animations?: Animation[]; + /** + * Metadata about the glTF asset + */ + asset: Asset; + /** + * An array of buffers. A buffer points to binary geometry, animation, or skins + */ + buffers?: Buffer[]; + /** + * An array of bufferViews. A bufferView is a view into a buffer generally representing a subset of the buffer + */ + bufferViews?: BufferView[]; + /** + * An array of cameras + */ + cameras?: Camera[]; + /** + * Names of glTF extensions used somewhere in this asset + */ + extensionsUsed?: string[]; + /** + * Names of glTF extensions required to properly load this asset + */ + extensionsRequired?: string[]; + /** + * An array of images. An image defines data used to create a texture + */ + images?: Image[]; + /** + * An array of materials. A material defines the appearance of a primitive + */ + materials?: Material[]; + /** + * An array of meshes. A mesh is a set of primitives to be rendered + */ + meshes?: Mesh[]; + /** + * An array of nodes + */ + nodes?: Node[]; + /** + * An array of samplers. A sampler contains properties for texture filtering and wrapping modes + */ + samplers?: Sampler[]; + /** + * The index of the default scene + */ + scene?: number; + /** + * An array of scenes + */ + scenes?: Scene[]; + /** + * An array of skins. A skin is defined by joints and matrices + */ + skins?: Skin[]; + /** + * An array of textures + */ + textures?: Texture[]; + } +} diff --git a/src/dataUriTextDocumentContentProvider.ts b/src/dataUriTextDocumentContentProvider.ts index 5394047..58acee1 100644 --- a/src/dataUriTextDocumentContentProvider.ts +++ b/src/dataUriTextDocumentContentProvider.ts @@ -1,124 +1,39 @@ -'use strict'; import * as vscode from 'vscode'; import * as Url from 'url'; -import * as path from 'path'; import * as fs from 'fs'; -import * as os from 'os'; import * as querystring from 'querystring'; import * as draco3dgltf from 'draco3dgltf'; import { getBuffer } from 'gltf-import-export'; import { sprintf } from 'sprintf-js'; -import { ExtensionContext, TextDocumentContentProvider, EventEmitter, Event, Uri, ViewColumn } from 'vscode'; +import { getFromJsonPointer, btoa, atob, AccessorTypeToNumComponents, getAccessorData } from './utilities'; +import { GLTF2 } from './GLTF2'; const decoderModule = draco3dgltf.createDecoderModule({}); -export function atob(str): string { - return Buffer.from(str, 'base64').toString('binary'); -} - -export function btoa(str): string { - return Buffer.from(str, 'binary').toString('base64'); -} - -export function getFromJsonPointer(glTF, jsonPointer : string) { - const jsonPointerSplit = jsonPointer.split('/'); - const numPointerSegments = jsonPointerSplit.length; - let result = glTF; - const firstValidIndex = 1; // Because the path has a leading slash. - for (let i = firstValidIndex; i < numPointerSegments; ++i) { - result = result[jsonPointerSplit[i]]; - } - return result; -} - -const gltfMimeTypes = { - 'image/png' : ['png'], - 'image/jpeg' : ['jpg', 'jpeg'], - 'image/vnd-ms.dds' : ['dds'], - 'text/plain' : ['glsl', 'vert', 'vs', 'frag', 'fs', 'txt'] -}; - -export enum ComponentType { - BYTE = 5120, - UNSIGNED_BYTE = 5121, - SHORT = 5122, - UNSIGNED_SHORT = 5123, - UNSIGNED_INT = 5125, - FLOAT = 5126 -}; - -export const ComponentTypeToBytesPerElement = { - 5120: Int8Array.BYTES_PER_ELEMENT, - 5121: Uint8Array.BYTES_PER_ELEMENT, - 5122: Int16Array.BYTES_PER_ELEMENT, - 5123: Uint16Array.BYTES_PER_ELEMENT, - 5125: Uint32Array.BYTES_PER_ELEMENT, - 5126: Float32Array.BYTES_PER_ELEMENT -}; - -export enum AccessorType { - 'SCALAR', - 'VEC2', - 'VEC3', - 'VEC4', - 'MAT2', - 'MAT3', - 'MAT4' -}; - -export const AccessorTypeToNumComponents = { - SCALAR: 1, - VEC2: 2, - VEC3: 3, - VEC4: 4, - MAT2: 4, - MAT3: 9, - MAT4: 16 -}; - const MatrixSquare = { MAT2: 2, MAT3: 3, MAT4: 4 } -export function guessFileExtension(mimeType) { - if (gltfMimeTypes.hasOwnProperty(mimeType)) { - return '.' + gltfMimeTypes[mimeType][0]; - } - return '.bin'; -} - -export function guessMimeType(filename : string): string { - for (const mimeType in gltfMimeTypes) { - for (const extensionIndex in gltfMimeTypes[mimeType]) { - const extension = gltfMimeTypes[mimeType][extensionIndex]; - if (filename.toLowerCase().endsWith('.' + extension)) { - return mimeType; - } - } - } - return 'application/octet-stream'; -} - interface QueryDataUri { viewColumn?: string, previewHtml?: string, } -export class DataUriTextDocumentContentProvider implements TextDocumentContentProvider { - private _onDidChange = new EventEmitter(); - private _context: ExtensionContext; +export class DataUriTextDocumentContentProvider implements vscode.TextDocumentContentProvider { + private _onDidChange = new vscode.EventEmitter(); + private _context: vscode.ExtensionContext; public UriPrefix = 'gltf-dataUri:'; - constructor(context: ExtensionContext) { + constructor(context: vscode.ExtensionContext) { this._context = context; } - public uriIfNotDataUri(glTF, jsonPointer : string) : string { + public uriIfNotDataUri(glTF, jsonPointer: string): string { const data = getFromJsonPointer(glTF, jsonPointer); if ((typeof data === 'object') && data.hasOwnProperty('uri')) { - const uri : string = data.uri; + const uri: string = data.uri; if (!uri.startsWith('data:')) { return uri; } @@ -126,22 +41,22 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr return null; } - public isImage(jsonPointer : string) : boolean { + public isImage(jsonPointer: string): boolean { return jsonPointer.startsWith('/images/'); } - public isShader(jsonPointer: string) : boolean { + public isShader(jsonPointer: string): boolean { return jsonPointer.startsWith('/shaders/'); } - public isAccessor(jsonPointer: string) : boolean { + public isAccessor(jsonPointer: string): boolean { return jsonPointer.startsWith('/accessors/'); } - public async provideTextDocumentContent(uri: Uri): Promise { + public async provideTextDocumentContent(uri: vscode.Uri): Promise { const fileName = decodeURIComponent(uri.fragment); const query = querystring.parse(uri.query); - query.viewColumn = query.viewColumn || ViewColumn.Active.toString(); + query.viewColumn = query.viewColumn || vscode.ViewColumn.Active.toString(); let glTFContent: string; const document = vscode.workspace.textDocuments.find(e => e.uri.scheme === 'file' && e.fileName === fileName); if (document) { @@ -149,7 +64,7 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr } else { glTFContent = fs.readFileSync(fileName, 'UTF-8'); } - const glTF = JSON.parse(glTFContent); + const glTF = JSON.parse(glTFContent) as GLTF2.GLTF; let jsonPointer = uri.path; if (this.isShader(jsonPointer) && jsonPointer.endsWith('.glsl')) { jsonPointer = jsonPointer.substring(0, jsonPointer.length - 5); @@ -158,7 +73,7 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr if (data && (typeof data === 'object')) { if (data.hasOwnProperty('uri')) { - let dataUri : string = data.uri; + let dataUri: string = data.uri; if (!dataUri.startsWith('data:')) { // Not a DataURI: Look up external reference. const name = decodeURI(Url.resolve(fileName, dataUri)); @@ -172,10 +87,10 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr // Go to Definition has a null activeTextEditor // Inspect Data Uri has a document that matches current but we provide a non-default viewColumn // In the last two cases we want to close the current (to be empty editor), for Peek we leave it open. - if (vscode.window.activeTextEditor == null || vscode.window.activeTextEditor.document != document || query.viewColumn != ViewColumn.Active.toString()) { + if (vscode.window.activeTextEditor == null || vscode.window.activeTextEditor.document != document || query.viewColumn != vscode.ViewColumn.Active.toString()) { vscode.commands.executeCommand('workbench.action.closeActiveEditor'); } - const previewUri: Uri = Uri.parse(this.UriPrefix + uri.path + '?previewHtml=true' + '#' + encodeURIComponent(fileName)); + const previewUri: vscode.Uri = vscode.Uri.parse(this.UriPrefix + uri.path + '?previewHtml=true' + '#' + encodeURIComponent(fileName)); await vscode.commands.executeCommand('vscode.previewHtml', previewUri, parseInt(query.viewColumn)); return ''; } else { @@ -190,7 +105,7 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr } else if (jsonPointer.startsWith('/accessors/')) { if (data.bufferView !== undefined) { let bufferView = glTF.bufferViews[data.bufferView]; - let buffer = getBuffer(glTF, bufferView.buffer, fileName); + let buffer = getBuffer(glTF, bufferView.buffer.toString(), fileName); return formatAccessor(buffer, data, bufferView); } else { return 'Accessor does not contain a bufferView'; @@ -203,7 +118,7 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr return 'Unknown:\n' + jsonPointer; } - private formatDraco(glTF: any, jsonPointer: string, fileName: string): string { + private formatDraco(glTF: GLTF2.GLTF, jsonPointer: string, fileName: string): string { const attrIndex = jsonPointer.lastIndexOf('/'); if (attrIndex == -1) { return 'Invalid path:\n' + jsonPointer; @@ -225,16 +140,15 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr const primitive = getFromJsonPointer(glTF, primitivePointer); const dracoExtension = primitive.extensions['KHR_draco_mesh_compression']; - let accessor; + let accessor: GLTF2.Accessor; if (attrName !== 'indices') { accessor = glTF.accessors[primitive.attributes[attrName]]; } let bufferView = glTF.bufferViews[dracoExtension.bufferView]; - let glTFBuffer = getBuffer(glTF, bufferView.buffer, fileName); + let glTFBuffer = getBuffer(glTF, bufferView.buffer.toString(), fileName); const bufferOffset: number = bufferView.byteOffset || 0; const bufferLength: number = bufferView.byteLength; - const bufferStride: number = bufferView.byteStride; const bufferViewBuf: Buffer = glTFBuffer.slice(bufferOffset, bufferOffset + bufferLength); const decoder = new decoderModule.Decoder(); @@ -253,7 +167,7 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr dracoGeometry = new decoderModule.Mesh(); status = decoder.DecodeBufferToMesh(dracoBuffer, dracoGeometry); break; - case decoderModule.POINT_CLOUD: + case decoderModule.POINT_CLOUD: if (attrName === 'indices') { return "Indices only valid for TRIANGULAR_MESH geometry."; } @@ -287,7 +201,7 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr if (i % numComponents == 0 && i !== 0) { result += '\n'; } - if (accessor.componentType === ComponentType.FLOAT) { + if (accessor.componentType === GLTF2.AccessorComponentType.FLOAT) { result += sprintf('%11.5f', value) + ' '; } else { result += sprintf('%5d', value) + ' '; @@ -310,43 +224,17 @@ export class DataUriTextDocumentContentProvider implements TextDocumentContentPr } } - get onDidChange(): Event { + get onDidChange(): vscode.Event { return this._onDidChange.event; } - public update(uri: Uri) { + public update(uri: vscode.Uri) { this._onDidChange.fire(uri); } } -function buildArrayBuffer(arrayType: any, data: Buffer, byteOffset: number, count: number, numComponents: number, byteStride?: number): any { - byteOffset += data.byteOffset; - const targetLength = count * numComponents; - - if (byteStride == null || byteStride === numComponents * arrayType.BYTES_PER_ELEMENT) { - return new arrayType(data.buffer, byteOffset, targetLength); - } - - const elementStride = byteStride / arrayType.BYTES_PER_ELEMENT; - const sourceBuffer = new arrayType(data.buffer, byteOffset, elementStride * count); - const targetBuffer = new arrayType(targetLength); - let sourceIndex = 0; - let targetIndex = 0; - - while (targetIndex < targetLength) { - for (let componentIndex = 0; componentIndex < numComponents; componentIndex++) { - targetBuffer[targetIndex] = sourceBuffer[sourceIndex + componentIndex]; - targetIndex++; - } - - sourceIndex += elementStride; - } - - return targetBuffer; -} - -function formatAccessor(buffer: Buffer, accessor: any, bufferView: any, normalizeOverride?: boolean): string { - let normalize: boolean = accessor.normalized === undefined ? (normalizeOverride == undefined ? (parseInt(accessor.componentType) == ComponentType.FLOAT) : normalizeOverride) : accessor.normalized; +function formatAccessor(buffer: Buffer, accessor: GLTF2.Accessor, bufferView: GLTF2.BufferView, normalizeOverride?: boolean): string { + let normalize: boolean = accessor.normalized === undefined ? (normalizeOverride == undefined ? (accessor.componentType == GLTF2.AccessorComponentType.FLOAT) : normalizeOverride) : accessor.normalized; let result: string = ''; function formatNumber(value: number, index: number, array: any) { @@ -358,19 +246,19 @@ function formatAccessor(buffer: Buffer, accessor: any, bufferView: any, normaliz } if (normalize) { - switch (parseInt(accessor.componentType)) { - case ComponentType.BYTE: - value = Math.max(value / 127.0, -1.0); - break; - case ComponentType.UNSIGNED_BYTE: - value = value / 255.0; - break; - case ComponentType.SHORT: - value = Math.max(value / 32767.0, -1.0); - break; - case ComponentType.UNSIGNED_SHORT: - value = value / 65535.0; - break; + switch (accessor.componentType) { + case GLTF2.AccessorComponentType.BYTE: + value = Math.max(value / 127.0, -1.0); + break; + case GLTF2.AccessorComponentType.UNSIGNED_BYTE: + value = value / 255.0; + break; + case GLTF2.AccessorComponentType.SHORT: + value = Math.max(value / 32767.0, -1.0); + break; + case GLTF2.AccessorComponentType.UNSIGNED_SHORT: + value = value / 65535.0; + break; } result += sprintf('%11.5f', value) + ' '; } else { @@ -378,36 +266,8 @@ function formatAccessor(buffer: Buffer, accessor: any, bufferView: any, normaliz } } - const arrayBuffer = getAccessorArrayBuffer(buffer, accessor, bufferView); - arrayBuffer.forEach(formatNumber); + const data = getAccessorData(accessor, bufferView, buffer); + data.forEach(formatNumber); return result; } - -export function getAccessorArrayBuffer(buffer: Buffer, accessor: any, bufferView: any): any { - const bufferOffset: number = bufferView.byteOffset || 0; - const bufferLength: number = bufferView.byteLength; - const bufferStride: number = bufferView.byteStride; - const bufferViewBuf: Buffer = buffer.slice(bufferOffset, bufferOffset + bufferLength); - const accessorByteOffset: number = accessor.byteOffset || 0; - - switch (parseInt(accessor.componentType)) { - case ComponentType.BYTE: - return buildArrayBuffer(Int8Array, bufferViewBuf, bufferOffset + accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); - - case ComponentType.UNSIGNED_BYTE: - return buildArrayBuffer(Uint8Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); - - case ComponentType.SHORT: - return buildArrayBuffer(Int16Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); - - case ComponentType.UNSIGNED_SHORT: - return buildArrayBuffer(Int16Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); - - case ComponentType.UNSIGNED_INT: - return buildArrayBuffer(Uint32Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); - - case ComponentType.FLOAT: - return buildArrayBuffer(Float32Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); - } -} diff --git a/src/extension.ts b/src/extension.ts index 0e13931..60cedf5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,10 +1,6 @@ -'use strict'; -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode'; -import { Uri, ViewColumn } from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; -import { DataUriTextDocumentContentProvider, getFromJsonPointer, btoa, guessMimeType, guessFileExtension, getAccessorArrayBuffer, AccessorTypeToNumComponents } from './dataUriTextDocumentContentProvider'; +import { DataUriTextDocumentContentProvider } from './dataUriTextDocumentContentProvider'; import { GltfPreviewDocumentContentProvider } from './gltfPreviewDocumentContentProvider'; import { GltfOutlineTreeDataProvider } from './gltfOutlineTreeDataProvider'; import { ConvertGLBtoGltfLoadFirst, ConvertToGLB, getBuffer } from 'gltf-import-export'; @@ -13,13 +9,15 @@ import * as jsonMap from 'json-source-map'; import * as path from 'path'; import * as Url from 'url'; import * as fs from 'fs'; +import { getFromJsonPointer, guessMimeType, btoa, guessFileExtension, getAccessorData, AccessorTypeToNumComponents } from './utilities'; +import { GLTF2 } from './GLTF2'; -function checkValidEditor() : boolean { +function checkValidEditor(): boolean { if (vscode.window.activeTextEditor === undefined) { vscode.window.showErrorMessage('Document too large (or no editor selected). ' + 'Click \'More info\' for details via GitHub.', 'More info').then(choice => { if (choice === 'More info') { - let uri = Uri.parse('https://github.com/AnalyticalGraphicsInc/gltf-vscode/blob/master/README.md#compatibiliy-and-known-size-limitations'); + let uri = vscode.Uri.parse('https://github.com/AnalyticalGraphicsInc/gltf-vscode/blob/master/README.md#compatibiliy-and-known-size-limitations'); vscode.commands.executeCommand('vscode.open', uri); } }); @@ -28,7 +26,7 @@ function checkValidEditor() : boolean { return true; } -function pointerContains(pointer: any, selection: vscode.Selection) : boolean { +function pointerContains(pointer: any, selection: vscode.Selection): boolean { const doc = vscode.window.activeTextEditor.document; const range = new vscode.Range(doc.positionAt(pointer.value.pos), doc.positionAt(pointer.valueEnd.pos)); @@ -37,7 +35,7 @@ function pointerContains(pointer: any, selection: vscode.Selection) : boolean { function tryGetJsonMap() { try { - return jsonMap.parse(vscode.window.activeTextEditor.document.getText()); + return jsonMap.parse(vscode.window.activeTextEditor.document.getText()) as { data: GLTF2.GLTF, pointers: Array }; } catch (ex) { vscode.window.showErrorMessage('Error parsing this document. Please make sure it is valid JSON.'); } @@ -48,7 +46,7 @@ function tryGetCurrentJsonPointer(map) { const selection = vscode.window.activeTextEditor.selection; const pointers = map.pointers; - let bestKey : string, secondBestKey : string; + let bestKey: string, secondBestKey: string; for (let key of Object.keys(pointers)) { let pointer = pointers[key]; if (pointerContains(pointer, selection)) { @@ -85,14 +83,14 @@ export function activateServer(context: vscode.ExtensionContext) { // If the extension is launched in debug mode then the debug server options are used // Otherwise the run options are used let serverOptions: ServerOptions = { - run : { module: serverModule, transport: TransportKind.ipc }, + run: { module: serverModule, transport: TransportKind.ipc }, debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } } // Options to control the language client let clientOptions: LanguageClientOptions = { // Register the server for plain text documents - documentSelector: [{scheme: 'file', language: 'json'}], + documentSelector: [{ scheme: 'file', language: 'json' }], synchronize: { // Synchronize the setting section 'glTF' to the server configurationSection: 'glTF', @@ -168,17 +166,17 @@ export function activate(context: vscode.ExtensionContext) { } if (notDataUri && !isImage) { - let finalUri = Uri.file(Url.resolve(vscode.window.activeTextEditor.document.fileName, notDataUri)); - await vscode.commands.executeCommand('vscode.open', finalUri, ViewColumn.Two); + let finalUri = vscode.Uri.file(Url.resolve(vscode.window.activeTextEditor.document.fileName, notDataUri)); + await vscode.commands.executeCommand('vscode.open', finalUri, vscode.ViewColumn.Two); } else { // This is a data: type uri if (isShader) { jsonPointer += '.glsl'; } - const previewUri = Uri.parse(dataPreviewProvider.UriPrefix + jsonPointer + '?viewColumn=' + ViewColumn.Two + '#' + + const previewUri = vscode.Uri.parse(dataPreviewProvider.UriPrefix + jsonPointer + '?viewColumn=' + vscode.ViewColumn.Two + '#' + encodeURIComponent(vscode.window.activeTextEditor.document.fileName)); - await vscode.commands.executeCommand('vscode.open', previewUri, ViewColumn.Two); + await vscode.commands.executeCommand('vscode.open', previewUri, vscode.ViewColumn.Two); dataPreviewProvider.update(previewUri); } })); @@ -203,7 +201,7 @@ export function activate(context: vscode.ExtensionContext) { const activeTextEditor = vscode.window.activeTextEditor; const data = getFromJsonPointer(map.data, bestKey); - let dataUri : string = data.uri; + let dataUri: string = data.uri; if (dataUri.startsWith('data:')) { vscode.window.showWarningMessage('This field is already a dataURI.'); } else { @@ -229,7 +227,7 @@ export function activate(context: vscode.ExtensionContext) { // // Export a Data URI to a file. // - function exportToFile(filename : string, pathFilename : string, pointer, dataUri : string) { + function exportToFile(filename: string, pathFilename: string, pointer, dataUri: string) { const pos = dataUri.indexOf(','); const fileContents = Buffer.from(dataUri.substring(pos + 1), 'base64'); @@ -264,7 +262,7 @@ export function activate(context: vscode.ExtensionContext) { const activeTextEditor = vscode.window.activeTextEditor; const data = getFromJsonPointer(map.data, bestKey); - let dataUri : string = data.uri; + let dataUri: string = data.uri; if (!dataUri.startsWith('data:')) { vscode.window.showWarningMessage('This field is not a dataURI.'); } else { @@ -283,10 +281,9 @@ export function activate(context: vscode.ExtensionContext) { let pathGuessName = path.join(path.dirname(activeTextEditor.document.fileName), guessName); const pointer = map.pointers[bestKey + '/uri']; - if (!vscode.workspace.getConfiguration('glTF').get('alwaysOverwriteDefaultFilename')) - { + if (!vscode.workspace.getConfiguration('glTF').get('alwaysOverwriteDefaultFilename')) { let options: vscode.SaveDialogOptions = { - defaultUri: Uri.file(pathGuessName), + defaultUri: vscode.Uri.file(pathGuessName), filters: { 'All files': ['*'] } @@ -316,7 +313,7 @@ export function activate(context: vscode.ExtensionContext) { } let gltfContent = te.document.getText(); - let gltf; + let gltf: GLTF2.GLTF; try { gltf = JSON.parse(gltfContent); } catch (ex) { @@ -332,7 +329,7 @@ export function activate(context: vscode.ExtensionContext) { let glbPath = editor.document.uri.fsPath.replace('.gltf', '.glb'); if (!vscode.workspace.getConfiguration('glTF').get('alwaysOverwriteDefaultFilename')) { const options: vscode.SaveDialogOptions = { - defaultUri: Uri.file(glbPath), + defaultUri: vscode.Uri.file(glbPath), filters: { 'Binary glTF': ['glb'], 'All files': ['*'] @@ -371,10 +368,10 @@ export function activate(context: vscode.ExtensionContext) { const fileName = vscode.window.activeTextEditor.document.fileName; const baseName = path.basename(fileName); - const gltfPreviewUri = Uri.parse(gltfPreviewProvider.UriPrefix + encodeURIComponent(fileName)); + const gltfPreviewUri = vscode.Uri.parse(gltfPreviewProvider.UriPrefix + encodeURIComponent(fileName)); - vscode.commands.executeCommand('vscode.previewHtml', gltfPreviewUri, ViewColumn.Two, `glTF Preview [${baseName}]`) - .then((success) => {}, (reason) => { vscode.window.showErrorMessage(reason); }); + vscode.commands.executeCommand('vscode.previewHtml', gltfPreviewUri, vscode.ViewColumn.Two, `glTF Preview [${baseName}]`) + .then((success) => { }, (reason) => { vscode.window.showErrorMessage(reason); }); // This can be used to debug the preview HTML. //vscode.workspace.openTextDocument(gltfPreviewUri).then((doc: vscode.TextDocument) => { @@ -401,7 +398,7 @@ export function activate(context: vscode.ExtensionContext) { if ((vscode.window.activeTextEditor !== undefined) && (vscode.window.activeTextEditor.document.uri.fsPath.endsWith('.glb'))) { fileUri = vscode.window.activeTextEditor.document.uri; - } else { + } else { const options: vscode.OpenDialogOptions = { canSelectMany: false, openLabel: 'Import', @@ -432,7 +429,7 @@ export function activate(context: vscode.ExtensionContext) { let targetFilename = fileUri.fsPath.replace('.glb', '.gltf'); if (!vscode.workspace.getConfiguration('glTF').get('alwaysOverwriteDefaultFilename')) { const options: vscode.SaveDialogOptions = { - defaultUri: Uri.file(targetFilename), + defaultUri: vscode.Uri.file(targetFilename), filters: { 'glTF': ['gltf'], 'All files': ['*'] @@ -449,7 +446,7 @@ export function activate(context: vscode.ExtensionContext) { let targetFilename = await ConvertGLBtoGltfLoadFirst(fileUri.fsPath, getTargetFilename); if (targetFilename != null) { - vscode.commands.executeCommand('vscode.open', Uri.file(targetFilename)); + vscode.commands.executeCommand('vscode.open', vscode.Uri.file(targetFilename)); } } catch (ex) { vscode.window.showErrorMessage(ex.toString()); @@ -486,7 +483,7 @@ export function activate(context: vscode.ExtensionContext) { } })); - function getAnimationFromJsonPointer(glTF, jsonPointer : string): { json: any, path: string } { + function getAnimationFromJsonPointer(glTF, jsonPointer: string): { json: any, path: string } { let inAnimation = false; let inSampler = false; const jsonPointerSplit = jsonPointer.split('/'); @@ -539,8 +536,8 @@ export function activate(context: vscode.ExtensionContext) { let accessorValues = []; if (accessor != undefined) { const bufferView = glTF.bufferViews[accessor.bufferView]; - const buffer = getBuffer(glTF, bufferView.buffer, activeTextEditor.document.fileName); - accessorValues = getAccessorArrayBuffer(buffer, accessor, bufferView); + const buffer = getBuffer(glTF, bufferView.buffer.toString(), activeTextEditor.document.fileName); + accessorValues = getAccessorData(accessor, bufferView, buffer); } animationPointer.json.extras[`vscode_gltf_${key}`] = Array.from(accessorValues); animationPointer.json.extras['vscode_gltf_type'] = accessor ? accessor.type : 'SCALAR'; @@ -682,7 +679,7 @@ export function activate(context: vscode.ExtensionContext) { throw new Error('Float32Array not 4 byte length'); } bufferOffset += float32Values.byteLength; - outputBuffers.push(Buffer.from(float32Values.buffer)); + outputBuffers.push(Buffer.from(float32Values.buffer as ArrayBuffer)); } const finalBuffer = Buffer.concat(outputBuffers); @@ -709,7 +706,7 @@ export function activate(context: vscode.ExtensionContext) { // vscode.workspace.onDidSaveTextDocument((document: vscode.TextDocument) => { if (document === vscode.window.activeTextEditor.document) { - const gltfPreviewUri = Uri.parse(gltfPreviewProvider.UriPrefix + encodeURIComponent(document.fileName)); + const gltfPreviewUri = vscode.Uri.parse(gltfPreviewProvider.UriPrefix + encodeURIComponent(document.fileName)); gltfPreviewProvider.update(gltfPreviewUri); } }); diff --git a/src/gltfOutlineTreeDataProvider.ts b/src/gltfOutlineTreeDataProvider.ts index 588a8a7..32c8839 100644 --- a/src/gltfOutlineTreeDataProvider.ts +++ b/src/gltfOutlineTreeDataProvider.ts @@ -1,14 +1,13 @@ -'use strict'; import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; import * as Url from 'url'; import * as jsonMap from 'json-source-map'; -import * as assetInfo from './dataUriTextDocumentContentProvider'; import { sprintf } from 'sprintf-js'; -import { ExtensionContext, TextDocumentContentProvider, EventEmitter, Event, Uri, ViewColumn, Range } from 'vscode'; +import { GLTF2 } from './GLTF2'; +import { AccessorTypeToNumComponents, ComponentTypeToBytesPerElement } from './utilities'; -export declare type GltfNodeType = 'animation' | 'material' | 'mesh' | 'node' | 'scene' | 'skeleton' |'skin' | 'texture' | 'root'; +export declare type GltfNodeType = 'animation' | 'material' | 'mesh' | 'node' | 'scene' | 'skeleton' | 'skin' | 'texture' | 'root'; interface GltfNode { parent?: GltfNode; @@ -27,7 +26,7 @@ interface MeshInfo { export class GltfOutlineTreeDataProvider implements vscode.TreeDataProvider { private tree: GltfNode; private editor: vscode.TextEditor; - private gltf: any; + private gltf: GLTF2.GLTF; private pointers: any; private skinMap = new Map>(); // jointId (nodeId) to Set of skinIds private skeletonMap = new Map>(); // nodeId (skeleton) to Set of skinIds @@ -53,8 +52,7 @@ export class GltfOutlineTreeDataProvider implements vscode.TreeDataProvider(); - private _context: ExtensionContext; +export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentContentProvider { + private _onDidChange = new vscode.EventEmitter(); + private _context: vscode.ExtensionContext; private _mainHtml: string; private _babylonHtml: string; private _cesiumHtml: string; @@ -14,7 +12,7 @@ export class GltfPreviewDocumentContentProvider implements TextDocumentContentPr public UriPrefix = 'gltf-preview://'; - constructor(context: ExtensionContext) { + constructor(context: vscode.ExtensionContext) { this._context = context; this._mainHtml = fs.readFileSync(this._context.asAbsolutePath('pages/previewModel.html'), 'UTF-8') this._babylonHtml = encodeURI(fs.readFileSync(this._context.asAbsolutePath('pages/babylonView.html'), 'UTF-8')); @@ -49,7 +47,7 @@ export class GltfPreviewDocumentContentProvider implements TextDocumentContentPr // click the pull-down and change "top" to "active-frame (webview.html)". // Now you can debug the HTML preview in the sandboxed iframe. - public provideTextDocumentContent(uri: Uri): string { + public provideTextDocumentContent(uri: vscode.Uri): string { let filePath = decodeURIComponent(uri.authority); const document = vscode.workspace.textDocuments.find(e => e.fileName.toLowerCase() === filePath.toLowerCase()); if (!document) { @@ -60,7 +58,7 @@ export class GltfPreviewDocumentContentProvider implements TextDocumentContentPr const gltfContent = document.getText(); const gltfFileName = path.basename(filePath); - let gltfRootPath : string = this.toUrl(path.dirname(filePath)); + let gltfRootPath: string = this.toUrl(path.dirname(filePath)); if (!gltfRootPath.endsWith("/")) { gltfRootPath += "/"; } @@ -73,7 +71,7 @@ export class GltfPreviewDocumentContentProvider implements TextDocumentContentPr } } catch (ex) { } - let extensionRootPath : string = this._context.asAbsolutePath('').replace(/\\/g, '/'); + let extensionRootPath: string = this._context.asAbsolutePath('').replace(/\\/g, '/'); if (!extensionRootPath.endsWith("/")) { extensionRootPath += "/"; } @@ -135,11 +133,11 @@ export class GltfPreviewDocumentContentProvider implements TextDocumentContentPr return content; } - get onDidChange(): Event { + get onDidChange(): vscode.Event { return this._onDidChange.event; } - public update(uri: Uri) { + public update(uri: vscode.Uri) { this._onDidChange.fire(uri); } } diff --git a/src/utilities.ts b/src/utilities.ts new file mode 100644 index 0000000..df8d6ff --- /dev/null +++ b/src/utilities.ts @@ -0,0 +1,126 @@ +import { GLTF2 } from "./glTF2"; + +export const ComponentTypeToBytesPerElement = { + 5120: Int8Array.BYTES_PER_ELEMENT, + 5121: Uint8Array.BYTES_PER_ELEMENT, + 5122: Int16Array.BYTES_PER_ELEMENT, + 5123: Uint16Array.BYTES_PER_ELEMENT, + 5125: Uint32Array.BYTES_PER_ELEMENT, + 5126: Float32Array.BYTES_PER_ELEMENT +}; + +export const AccessorTypeToNumComponents = { + SCALAR: 1, + VEC2: 2, + VEC3: 3, + VEC4: 4, + MAT2: 4, + MAT3: 9, + MAT4: 16 +}; + +export function getFromJsonPointer(glTF, jsonPointer: string) { + const jsonPointerSplit = jsonPointer.split('/'); + const numPointerSegments = jsonPointerSplit.length; + let result = glTF; + const firstValidIndex = 1; // Because the path has a leading slash. + for (let i = firstValidIndex; i < numPointerSegments; ++i) { + result = result[jsonPointerSplit[i]]; + } + return result; +} + +export function truncateJsonPointer(value: string, level: number): string { + const components = value.split('/'); + components.splice(level + 1); + return components.join('/'); +} + +export function atob(str): string { + return Buffer.from(str, 'base64').toString('binary'); +} + +export function btoa(str): string { + return Buffer.from(str, 'binary').toString('base64'); +} + +function buildArrayBuffer(typedArray: any, data: ArrayBufferView, byteOffset: number, count: number, numComponents: number, byteStride?: number): T { + byteOffset += data.byteOffset; + + const targetLength = count * numComponents; + + if (byteStride == null || byteStride === numComponents * typedArray.BYTES_PER_ELEMENT) { + return new typedArray(data.buffer, byteOffset, targetLength); + } + + const elementStride = byteStride / typedArray.BYTES_PER_ELEMENT; + const sourceBuffer = new typedArray(data.buffer, byteOffset, elementStride * count); + const targetBuffer = new typedArray(targetLength); + let sourceIndex = 0; + let targetIndex = 0; + + while (targetIndex < targetLength) { + for (let componentIndex = 0; componentIndex < numComponents; componentIndex++) { + targetBuffer[targetIndex] = sourceBuffer[sourceIndex + componentIndex]; + targetIndex++; + } + + sourceIndex += elementStride; + } + + return targetBuffer; +} + +export function getAccessorData(accessor: GLTF2.Accessor, bufferView: GLTF2.BufferView, buffer: Buffer): any { + const bufferOffset: number = bufferView.byteOffset || 0; + const bufferLength: number = bufferView.byteLength; + const bufferStride: number = bufferView.byteStride; + const bufferViewBuf: ArrayBufferView = buffer.subarray(bufferOffset, bufferOffset + bufferLength); + const accessorByteOffset: number = accessor.byteOffset || 0; + + switch (accessor.componentType) { + case GLTF2.AccessorComponentType.BYTE: + return buildArrayBuffer(Int8Array, bufferViewBuf, bufferOffset + accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); + + case GLTF2.AccessorComponentType.UNSIGNED_BYTE: + return buildArrayBuffer(Uint8Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); + + case GLTF2.AccessorComponentType.SHORT: + return buildArrayBuffer(Int16Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); + + case GLTF2.AccessorComponentType.UNSIGNED_SHORT: + return buildArrayBuffer(Int16Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); + + case GLTF2.AccessorComponentType.UNSIGNED_INT: + return buildArrayBuffer(Uint32Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); + + case GLTF2.AccessorComponentType.FLOAT: + return buildArrayBuffer(Float32Array, bufferViewBuf, accessorByteOffset, accessor.count, AccessorTypeToNumComponents[accessor.type], bufferStride); + } +} + +const gltfMimeTypes = { + 'image/png': ['png'], + 'image/jpeg': ['jpg', 'jpeg'], + 'image/vnd-ms.dds': ['dds'], + 'text/plain': ['glsl', 'vert', 'vs', 'frag', 'fs', 'txt'] +}; + +export function guessFileExtension(mimeType) { + if (gltfMimeTypes.hasOwnProperty(mimeType)) { + return '.' + gltfMimeTypes[mimeType][0]; + } + return '.bin'; +} + +export function guessMimeType(filename: string): string { + for (const mimeType in gltfMimeTypes) { + for (const extensionIndex in gltfMimeTypes[mimeType]) { + const extension = gltfMimeTypes[mimeType][extensionIndex]; + if (filename.toLowerCase().endsWith('.' + extension)) { + return mimeType; + } + } + } + return 'application/octet-stream'; +} diff --git a/src/validationProvider.ts b/src/validationProvider.ts index d64cd96..877a0f4 100644 --- a/src/validationProvider.ts +++ b/src/validationProvider.ts @@ -1,10 +1,7 @@ -'use strict'; import * as vscode from 'vscode'; -import * as Url from 'url'; import * as fs from 'fs'; import * as path from 'path'; import * as gltfValidator from 'gltf-validator'; -import { resolve } from 'url'; const SaveReportAs = 'Save Report As...'; const OverwriteReport = 'Save report.json'; @@ -58,7 +55,7 @@ export async function validate(sourceFilename: string) { resolve(data); }); } - ), + ), }); const useSaveAs = !vscode.workspace.getConfiguration('glTF').get('alwaysOverwriteDefaultFilename'); @@ -70,14 +67,14 @@ export async function validate(sourceFilename: string) { let userChoicePromise: Thenable; if (result.issues.numErrors > 0 && result.issues.numWarnings > 0) { userChoicePromise = vscode.window.showErrorMessage('glTF Validator found ' + - messageLabel(result.issues.numErrors, 'errors') + ' and ' + - messageLabel(result.issues.numWarnings, 'warnings.'), SaveReport); + messageLabel(result.issues.numErrors, 'errors') + ' and ' + + messageLabel(result.issues.numWarnings, 'warnings.'), SaveReport); } else if (result.issues.numErrors > 0) { userChoicePromise = vscode.window.showErrorMessage('glTF Validator found ' + - messageLabel(result.issues.numErrors, 'errors.'), SaveReport); + messageLabel(result.issues.numErrors, 'errors.'), SaveReport); } else if (result.issues.numWarnings > 0) { userChoicePromise = vscode.window.showWarningMessage('glTF Validator found ' + - messageLabel(result.issues.numWarnings, 'warnings.'), SaveReport); + messageLabel(result.issues.numWarnings, 'warnings.'), SaveReport); } else if (result.issues.numInfos > 0) { userChoicePromise = vscode.window.showWarningMessage('glTF Validator added information to its report.', SaveReport); } else { diff --git a/tsconfig.json b/tsconfig.json index 8bbb437..547259b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,12 @@ { "compilerOptions": { + "alwaysStrict": true, + "noImplicitAny": false, + "noImplicitReturns": true, "module": "commonjs", - "target": "es6", + "target": "es2017", "outDir": "out", - "lib": [ - "es6" - ], + "lib": [ "es2017" ], "sourceMap": true, "rootDir": "." }, From e9ddd033384b7fb496700a89f22e7cda75ede461 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Sun, 4 Nov 2018 16:00:03 -0800 Subject: [PATCH 2/5] Switch to webview for gltf preview --- src/contextBase.ts | 16 +++ src/extension.ts | 37 ++--- ...umentContentProvider.ts => gltfPreview.ts} | 135 +++++++++--------- src/utilities.ts | 10 ++ 4 files changed, 104 insertions(+), 94 deletions(-) create mode 100644 src/contextBase.ts rename src/{gltfPreviewDocumentContentProvider.ts => gltfPreview.ts} (54%) diff --git a/src/contextBase.ts b/src/contextBase.ts new file mode 100644 index 0000000..2eb1a46 --- /dev/null +++ b/src/contextBase.ts @@ -0,0 +1,16 @@ +import * as vscode from "vscode"; +import { toResourceUrl } from "./utilities"; + +export abstract class ContextBase { + protected readonly _context: vscode.ExtensionContext; + private readonly _extensionRootPath: string; + + constructor(context: vscode.ExtensionContext) { + this._context = context; + this._extensionRootPath = `${toResourceUrl(this._context.extensionPath.replace(/\\$/, ''))}/`; + } + + protected get extensionRootPath(): string { + return this._extensionRootPath; + } +} diff --git a/src/extension.ts b/src/extension.ts index 60cedf5..cb1a479 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,15 +1,14 @@ import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; import { DataUriTextDocumentContentProvider } from './dataUriTextDocumentContentProvider'; -import { GltfPreviewDocumentContentProvider } from './gltfPreviewDocumentContentProvider'; import { GltfOutlineTreeDataProvider } from './gltfOutlineTreeDataProvider'; +import { GltfPreview } from './gltfPreview'; import { ConvertGLBtoGltfLoadFirst, ConvertToGLB, getBuffer } from 'gltf-import-export'; import * as GltfValidate from './validationProvider'; -import * as jsonMap from 'json-source-map'; import * as path from 'path'; import * as Url from 'url'; import * as fs from 'fs'; -import { getFromJsonPointer, guessMimeType, btoa, guessFileExtension, getAccessorData, AccessorTypeToNumComponents } from './utilities'; +import { getFromJsonPointer, guessMimeType, btoa, guessFileExtension, getAccessorData, AccessorTypeToNumComponents, parseJsonMap } from './utilities'; import { GLTF2 } from './GLTF2'; function checkValidEditor(): boolean { @@ -35,7 +34,7 @@ function pointerContains(pointer: any, selection: vscode.Selection): boolean { function tryGetJsonMap() { try { - return jsonMap.parse(vscode.window.activeTextEditor.document.getText()) as { data: GLTF2.GLTF, pointers: Array }; + return parseJsonMap(vscode.window.activeTextEditor.document.getText()); } catch (ex) { vscode.window.showErrorMessage('Error parsing this document. Please make sure it is valid JSON.'); } @@ -354,32 +353,17 @@ export function activate(context: vscode.ExtensionContext) { } })); + const gltfPreview = new GltfPreview(context); + // - // Register a preview of the whole glTF file. + // Preview a glTF model. // - const gltfPreviewProvider = new GltfPreviewDocumentContentProvider(context); - const gltfPreviewRegistration = vscode.workspace.registerTextDocumentContentProvider('gltf-preview', gltfPreviewProvider); - context.subscriptions.push(gltfPreviewRegistration); - context.subscriptions.push(vscode.commands.registerCommand('gltf.previewModel', () => { if (!checkValidEditor()) { return; } - const fileName = vscode.window.activeTextEditor.document.fileName; - const baseName = path.basename(fileName); - const gltfPreviewUri = vscode.Uri.parse(gltfPreviewProvider.UriPrefix + encodeURIComponent(fileName)); - - vscode.commands.executeCommand('vscode.previewHtml', gltfPreviewUri, vscode.ViewColumn.Two, `glTF Preview [${baseName}]`) - .then((success) => { }, (reason) => { vscode.window.showErrorMessage(reason); }); - - // This can be used to debug the preview HTML. - //vscode.workspace.openTextDocument(gltfPreviewUri).then((doc: vscode.TextDocument) => { - // vscode.window.showTextDocument(doc, ViewColumn.Three, false).then(e => { - // }); - //}, (reason) => { vscode.window.showErrorMessage(reason); }); - - gltfPreviewProvider.update(gltfPreviewUri); + gltfPreview.showPanel(vscode.window.activeTextEditor.document); })); // @@ -704,11 +688,8 @@ export function activate(context: vscode.ExtensionContext) { // // Update all preview windows when the glTF file is saved. // - vscode.workspace.onDidSaveTextDocument((document: vscode.TextDocument) => { - if (document === vscode.window.activeTextEditor.document) { - const gltfPreviewUri = vscode.Uri.parse(gltfPreviewProvider.UriPrefix + encodeURIComponent(document.fileName)); - gltfPreviewProvider.update(gltfPreviewUri); - } + vscode.workspace.onDidSaveTextDocument((document) => { + gltfPreview.updatePanel(document); }); } diff --git a/src/gltfPreviewDocumentContentProvider.ts b/src/gltfPreview.ts similarity index 54% rename from src/gltfPreviewDocumentContentProvider.ts rename to src/gltfPreview.ts index e0376e6..59ac934 100644 --- a/src/gltfPreviewDocumentContentProvider.ts +++ b/src/gltfPreview.ts @@ -1,37 +1,26 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; +import { ContextBase } from './contextBase'; +import { toResourceUrl, parseJsonMap } from './utilities'; -export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentContentProvider { - private _onDidChange = new vscode.EventEmitter(); - private _context: vscode.ExtensionContext; - private _mainHtml: string; - private _babylonHtml: string; - private _cesiumHtml: string; - private _threeHtml: string; +export class GltfPreview extends ContextBase { + private readonly _mainHtml: string; + private readonly _babylonHtml: string; + private readonly _cesiumHtml: string; + private readonly _threeHtml: string; - public UriPrefix = 'gltf-preview://'; + private _panels: { [fileName: string]: vscode.WebviewPanel } = {}; constructor(context: vscode.ExtensionContext) { - this._context = context; - this._mainHtml = fs.readFileSync(this._context.asAbsolutePath('pages/previewModel.html'), 'UTF-8') + super(context); + + this._mainHtml = fs.readFileSync(this._context.asAbsolutePath('pages/previewModel.html'), 'UTF-8'); this._babylonHtml = encodeURI(fs.readFileSync(this._context.asAbsolutePath('pages/babylonView.html'), 'UTF-8')); this._cesiumHtml = encodeURI(fs.readFileSync(this._context.asAbsolutePath('pages/cesiumView.html'), 'UTF-8')); this._threeHtml = encodeURI(fs.readFileSync(this._context.asAbsolutePath('pages/threeView.html'), 'UTF-8')); } - private addFilePrefix(file: string): string { - return ((file[0] === '/') ? 'file://' : 'file:///') + file; - } - - private getFilePath(file: string): string { - return this.addFilePrefix(this._context.asAbsolutePath(file)); - } - - private toUrl(file: string): string { - return this.addFilePrefix(file.replace(/\\/g, '/')); - } - // Instructions to open Chrome DevTools on the HTML preview window: // // 1. With the HTML preview window open, click Help->Toggle Developer Tools. @@ -47,53 +36,76 @@ export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentCo // click the pull-down and change "top" to "active-frame (webview.html)". // Now you can debug the HTML preview in the sandboxed iframe. - public provideTextDocumentContent(uri: vscode.Uri): string { - let filePath = decodeURIComponent(uri.authority); - const document = vscode.workspace.textDocuments.find(e => e.fileName.toLowerCase() === filePath.toLowerCase()); - if (!document) { - return 'ERROR: Can no longer find document in editor: ' + filePath; + public showPanel(gltfDocument: vscode.TextDocument): void { + const gltfFilePath = gltfDocument.fileName; + + let panel = this._panels[gltfFilePath]; + if (!panel) { + panel = vscode.window.createWebviewPanel('gltf.preview', 'glTF Preview', vscode.ViewColumn.Two, { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [ + vscode.Uri.file(this._context.extensionPath), + vscode.Uri.file(path.dirname(gltfFilePath)), + ], + }); + + panel.onDidDispose(() => { + delete this._panels[gltfFilePath]; + }); + + this._panels[gltfFilePath] = panel; } - // Update case of `fileName` to match actual document filename. - filePath = document.fileName; - - const gltfContent = document.getText(); - const gltfFileName = path.basename(filePath); - let gltfRootPath: string = this.toUrl(path.dirname(filePath)); - if (!gltfRootPath.endsWith("/")) { - gltfRootPath += "/"; + + const gltfContent = gltfDocument.getText(); + this.updatePanelInternal(panel, gltfFilePath, gltfContent); + panel.reveal(vscode.ViewColumn.Two); + } + + public updatePanel(gltfDocument: vscode.TextDocument): void { + const gltfFileName = gltfDocument.fileName; + let panel = this._panels[gltfFileName]; + if (panel) { + const gltfContent = gltfDocument.getText(); + this.updatePanelInternal(panel, gltfFileName, gltfContent); } + } + + private updatePanelInternal(panel: vscode.WebviewPanel, gltfFilePath: string, gltfContent: string): void { + const map = parseJsonMap(gltfContent); + + const gltfRootPath = toResourceUrl(`${path.dirname(gltfFilePath)}/`); + const gltfFileName = path.basename(gltfFilePath); - var gltfMajorVersion = 1; - try { - const gltf = JSON.parse(gltfContent); - if (gltf && gltf.asset && gltf.asset.version && gltf.asset.version[0] === '2') { - gltfMajorVersion = 2; - } - } catch (ex) { } - - let extensionRootPath: string = this._context.asAbsolutePath('').replace(/\\/g, '/'); - if (!extensionRootPath.endsWith("/")) { - extensionRootPath += "/"; + const gltf = map.data; + let gltfMajorVersion = 1; + if (gltf && gltf.asset && gltf.asset.version && gltf.asset.version[0] === '2') { + gltfMajorVersion = 2; } + panel.title = `glTF Preview [${gltfFileName}]`; + panel.webview.html = this.formatHtml(gltfMajorVersion, gltfContent, gltfRootPath, gltfFileName); + } + + private formatHtml(gltfMajorVersion: number, gltfContent: string, gltfRootPath: string, gltfFileName: string): string { const defaultEngine = vscode.workspace.getConfiguration('glTF').get('defaultV' + gltfMajorVersion + 'Engine'); const defaultBabylonReflection = String(vscode.workspace.getConfiguration('glTF.Babylon') - .get('environment')).replace('{extensionRootPath}', extensionRootPath.replace(/\/$/, '')); + .get('environment')).replace('{extensionRootPath}', this.extensionRootPath); const defaultThreeReflection = String(vscode.workspace.getConfiguration('glTF.Three') - .get('environment')).replace('{extensionRootPath}', extensionRootPath.replace(/\/$/, '')); - const dracoLoaderPath = extensionRootPath + 'engines/Draco/draco_decoder.js'; + .get('environment')).replace('{extensionRootPath}', this.extensionRootPath); + const dracoLoaderPath = this.extensionRootPath + 'engines/Draco/draco_decoder.js'; // These strings are available in JavaScript by looking up the ID. They provide the extension's root // path (needed for locating additional assets), various settings, and the glTF name and contents. // Some engines can display "live" glTF contents, others must load from the glTF path and filename. // The path name is needed for glTF files that include external resources. const strings = [ - { id: 'extensionRootPath', text: this.toUrl(extensionRootPath) }, + { id: 'extensionRootPath', text: this.extensionRootPath }, { id: 'defaultEngine', text: defaultEngine }, - { id: 'defaultBabylonReflection', text: this.toUrl(defaultBabylonReflection) }, - { id: 'defaultThreeReflection', text: this.toUrl(defaultThreeReflection) }, - { id: 'dracoLoaderPath', text: this.toUrl(dracoLoaderPath) }, + { id: 'defaultBabylonReflection', text: defaultBabylonReflection }, + { id: 'defaultThreeReflection', text: defaultThreeReflection }, + { id: 'dracoLoaderPath', text: dracoLoaderPath }, { id: 'babylonHtml', text: this._babylonHtml }, { id: 'cesiumHtml', text: this._cesiumHtml }, { id: 'threeHtml', text: this._threeHtml }, @@ -112,6 +124,7 @@ export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentCo const scripts = [ 'engines/Cesium/Cesium.js', 'node_modules/babylonjs/babylon.max.js', + 'node_modules/babylonjs/babylon.inspector.min.js', 'node_modules/babylonjs-loaders/babylonjs.loaders.js', 'engines/Three/three.min.js', 'engines/Three/DDSLoader.js', @@ -125,19 +138,9 @@ export class GltfPreviewDocumentContentProvider implements vscode.TextDocumentCo ]; // Note that with the file: protocol, we must manually specify the UTF-8 charset. - const content = this._mainHtml.replace('{assets}', - styles.map(s => `\n`).join('') + + return this._mainHtml.replace('{assets}', + styles.map(s => `\n`).join('') + strings.map(s => `\n`).join('') + - scripts.map(s => `\n`).join('')); - - return content; - } - - get onDidChange(): vscode.Event { - return this._onDidChange.event; - } - - public update(uri: vscode.Uri) { - this._onDidChange.fire(uri); + scripts.map(s => `\n`).join('')); } } diff --git a/src/utilities.ts b/src/utilities.ts index df8d6ff..e629689 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,4 +1,5 @@ import { GLTF2 } from "./glTF2"; +import * as jsonMap from 'json-source-map'; export const ComponentTypeToBytesPerElement = { 5120: Int8Array.BYTES_PER_ELEMENT, @@ -124,3 +125,12 @@ export function guessMimeType(filename: string): string { } return 'application/octet-stream'; } + +export function toResourceUrl(path: string): string { + return `vscode-resource:${path.replace(/\\/g, '/')}`; +} + +export function parseJsonMap(content: string) { + return jsonMap.parse(content) as { data: GLTF2.GLTF, pointers: Array }; +} + From f234d64b8585acc61664ddcfeea89f22db94f1be Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Mon, 5 Nov 2018 15:48:47 -0800 Subject: [PATCH 3/5] Upgrade gltf-import-export module --- package.json | 2 +- src/dataUriTextDocumentContentProvider.ts | 4 ++-- src/extension.ts | 4 ++-- src/validationProvider.ts | 3 +-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index b8e8fad..37510dd 100644 --- a/package.json +++ b/package.json @@ -296,7 +296,7 @@ "babylonjs": "^3.2.0", "babylonjs-loaders": "^3.2.0", "draco3dgltf": "^1.3.4", - "gltf-import-export": "^1.0.12", + "gltf-import-export": "^1.0.13", "gltf-validator": "2.0.0-dev.2.5", "json-source-map": "^0.4.0", "sprintf-js": "^1.1.1", diff --git a/src/dataUriTextDocumentContentProvider.ts b/src/dataUriTextDocumentContentProvider.ts index 58acee1..cc4ab97 100644 --- a/src/dataUriTextDocumentContentProvider.ts +++ b/src/dataUriTextDocumentContentProvider.ts @@ -105,7 +105,7 @@ export class DataUriTextDocumentContentProvider implements vscode.TextDocumentCo } else if (jsonPointer.startsWith('/accessors/')) { if (data.bufferView !== undefined) { let bufferView = glTF.bufferViews[data.bufferView]; - let buffer = getBuffer(glTF, bufferView.buffer.toString(), fileName); + let buffer = getBuffer(glTF, bufferView.buffer, fileName); return formatAccessor(buffer, data, bufferView); } else { return 'Accessor does not contain a bufferView'; @@ -146,7 +146,7 @@ export class DataUriTextDocumentContentProvider implements vscode.TextDocumentCo } let bufferView = glTF.bufferViews[dracoExtension.bufferView]; - let glTFBuffer = getBuffer(glTF, bufferView.buffer.toString(), fileName); + let glTFBuffer = getBuffer(glTF, bufferView.buffer, fileName); const bufferOffset: number = bufferView.byteOffset || 0; const bufferLength: number = bufferView.byteLength; const bufferViewBuf: Buffer = glTFBuffer.slice(bufferOffset, bufferOffset + bufferLength); diff --git a/src/extension.ts b/src/extension.ts index cb1a479..a3d529a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -520,7 +520,7 @@ export function activate(context: vscode.ExtensionContext) { let accessorValues = []; if (accessor != undefined) { const bufferView = glTF.bufferViews[accessor.bufferView]; - const buffer = getBuffer(glTF, bufferView.buffer.toString(), activeTextEditor.document.fileName); + const buffer = getBuffer(glTF, bufferView.buffer, activeTextEditor.document.fileName); accessorValues = getAccessorData(accessor, bufferView, buffer); } animationPointer.json.extras[`vscode_gltf_${key}`] = Array.from(accessorValues); @@ -601,7 +601,7 @@ export function activate(context: vscode.ExtensionContext) { bufferIndex = bufferView.buffer; } const bufferJson = glTF.buffers[bufferIndex]; - const bufferData = getBuffer(glTF, bufferIndex.toString(), activeTextEditor.document.fileName); + const bufferData = getBuffer(glTF, bufferIndex, activeTextEditor.document.fileName); const alignedLength = (value: number) => { const alignValue = 4; if (value == 0) { diff --git a/src/validationProvider.ts b/src/validationProvider.ts index 877a0f4..0148708 100644 --- a/src/validationProvider.ts +++ b/src/validationProvider.ts @@ -54,8 +54,7 @@ export async function validate(sourceFilename: string) { } resolve(data); }); - } - ), + }), }); const useSaveAs = !vscode.workspace.getConfiguration('glTF').get('alwaysOverwriteDefaultFilename'); From 9b264b4ed5b1451e2aacab8d84ae2cb6501732d2 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Tue, 6 Nov 2018 10:55:25 -0800 Subject: [PATCH 4/5] Fix a couple of issues --- package.json | 6 +++--- src/utilities.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 37510dd..d7f8306 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "icon": "images/gltf.png", "engines": { - "vscode": "^1.17.0" + "vscode": "^1.23.0" }, "categories": [ "Formatters", @@ -54,12 +54,12 @@ }, "glTF.Babylon.environment": { "type": "string", - "default": "{extensionRootPath}/environments/babylon/countrySpecularHDR.dds", + "default": "{extensionRootPath}environments/babylon/countrySpecularHDR.dds", "description": "The path to a BabylonJS-ready DDS environment file. See: https://doc.babylonjs.com/how_to/physically_based_rendering#creating-a-dds-environment-file-from-an-hdr-image" }, "glTF.Three.environment": { "type": "string", - "default": "{extensionRootPath}/environments/threejs/Park2/{face}.jpg", + "default": "{extensionRootPath}environments/threejs/Park2/{face}.jpg", "description": "The path to a set of environment cube faces usable by ThreeJS. Use {face} for the face name, and six files must exist with the following face names: posx, negx, posy, negy, posz, and negz." }, "glTF.showToolbar3D": { diff --git a/src/utilities.ts b/src/utilities.ts index e629689..655105b 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,4 +1,4 @@ -import { GLTF2 } from "./glTF2"; +import { GLTF2 } from "./GLTF2"; import * as jsonMap from 'json-source-map'; export const ComponentTypeToBytesPerElement = { From 31293cf1ec468eb6eb58d535b422e4838dddf515 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Wed, 7 Nov 2018 15:35:12 -0800 Subject: [PATCH 5/5] Fix environment configuration setting --- src/contextBase.ts | 14 +++++++++++++- src/gltfPreview.ts | 41 ++++++++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/contextBase.ts b/src/contextBase.ts index 2eb1a46..f99b30e 100644 --- a/src/contextBase.ts +++ b/src/contextBase.ts @@ -1,4 +1,5 @@ import * as vscode from "vscode"; +import * as path from 'path'; import { toResourceUrl } from "./utilities"; export abstract class ContextBase { @@ -7,10 +8,21 @@ export abstract class ContextBase { constructor(context: vscode.ExtensionContext) { this._context = context; - this._extensionRootPath = `${toResourceUrl(this._context.extensionPath.replace(/\\$/, ''))}/`; + this._extensionRootPath = `${toResourceUrl(this._context.extensionPath)}/`; } protected get extensionRootPath(): string { return this._extensionRootPath; } + + protected getConfigResourceUrl(section: string, name: string, localResourceRoots: Array): string { + const value = vscode.workspace.getConfiguration(section).get(name); + + if (value.startsWith('{extensionRootPath}')) { + return value.replace(/^{extensionRootPath}/, this._extensionRootPath); + } + + localResourceRoots.push(vscode.Uri.file(path.dirname(value))); + return toResourceUrl(value.replace(/\\/, '/')); + } } diff --git a/src/gltfPreview.ts b/src/gltfPreview.ts index 59ac934..d6d6365 100644 --- a/src/gltfPreview.ts +++ b/src/gltfPreview.ts @@ -4,13 +4,18 @@ import * as fs from 'fs'; import { ContextBase } from './contextBase'; import { toResourceUrl, parseJsonMap } from './utilities'; +interface GltfPreviewPanel extends vscode.WebviewPanel { + _defaultBabylonReflection: string; + _defaultThreeReflection: string; +} + export class GltfPreview extends ContextBase { private readonly _mainHtml: string; private readonly _babylonHtml: string; private readonly _cesiumHtml: string; private readonly _threeHtml: string; - private _panels: { [fileName: string]: vscode.WebviewPanel } = {}; + private _panels: { [fileName: string]: GltfPreviewPanel } = {}; constructor(context: vscode.ExtensionContext) { super(context); @@ -41,19 +46,27 @@ export class GltfPreview extends ContextBase { let panel = this._panels[gltfFilePath]; if (!panel) { + const localResourceRoots = [ + vscode.Uri.file(this._context.extensionPath), + vscode.Uri.file(path.dirname(gltfFilePath)), + ]; + + const defaultBabylonReflection = this.getConfigResourceUrl('glTF.Babylon', 'environment', localResourceRoots); + const defaultThreeReflection = this.getConfigResourceUrl('glTF.Three', 'environment', localResourceRoots); + panel = vscode.window.createWebviewPanel('gltf.preview', 'glTF Preview', vscode.ViewColumn.Two, { enableScripts: true, retainContextWhenHidden: true, - localResourceRoots: [ - vscode.Uri.file(this._context.extensionPath), - vscode.Uri.file(path.dirname(gltfFilePath)), - ], - }); + localResourceRoots: localResourceRoots, + }) as GltfPreviewPanel; panel.onDidDispose(() => { delete this._panels[gltfFilePath]; }); + panel._defaultBabylonReflection = defaultBabylonReflection; + panel._defaultThreeReflection = defaultThreeReflection; + this._panels[gltfFilePath] = panel; } @@ -71,7 +84,7 @@ export class GltfPreview extends ContextBase { } } - private updatePanelInternal(panel: vscode.WebviewPanel, gltfFilePath: string, gltfContent: string): void { + private updatePanelInternal(panel: GltfPreviewPanel, gltfFilePath: string, gltfContent: string): void { const map = parseJsonMap(gltfContent); const gltfRootPath = toResourceUrl(`${path.dirname(gltfFilePath)}/`); @@ -84,16 +97,18 @@ export class GltfPreview extends ContextBase { } panel.title = `glTF Preview [${gltfFileName}]`; - panel.webview.html = this.formatHtml(gltfMajorVersion, gltfContent, gltfRootPath, gltfFileName); + panel.webview.html = this.formatHtml( + gltfMajorVersion, + gltfContent, + gltfRootPath, + gltfFileName, + panel._defaultBabylonReflection, + panel._defaultThreeReflection); } - private formatHtml(gltfMajorVersion: number, gltfContent: string, gltfRootPath: string, gltfFileName: string): string { + private formatHtml(gltfMajorVersion: number, gltfContent: string, gltfRootPath: string, gltfFileName: string, defaultBabylonReflection: string, defaultThreeReflection: string): string { const defaultEngine = vscode.workspace.getConfiguration('glTF').get('defaultV' + gltfMajorVersion + 'Engine'); - const defaultBabylonReflection = String(vscode.workspace.getConfiguration('glTF.Babylon') - .get('environment')).replace('{extensionRootPath}', this.extensionRootPath); - const defaultThreeReflection = String(vscode.workspace.getConfiguration('glTF.Three') - .get('environment')).replace('{extensionRootPath}', this.extensionRootPath); const dracoLoaderPath = this.extensionRootPath + 'engines/Draco/draco_decoder.js'; // These strings are available in JavaScript by looking up the ID. They provide the extension's root