volograms-js
Version:
Volograms JS Reader
195 lines (156 loc) • 7.38 kB
JavaScript
import { BinaryReader, TypedArrays } from './BinaryReader.js'
import {fetchOnProgress} from './utils.js'
/**
* Class loading and reading a volograms body containing frames/sequence (usually sequence_0.vols)
* This version first load and read the whole file skipping the frame data and build a frame directory
* containing pointers allowing to access directly a specific frame in memory
* Then it will load (copy or if possible only point) the geometry for a given frame number.
* It handles skipped keyframe.
* Previous version was creating all geometries, which could have caused memory issue when the volograms is big
*/
export class VologramBodyReader {
constructor(url, header, onProgress = () => {}) {
this.header = header
this._url = url
this.frameNumber = -1
this.lastKeyFrameNumber = undefined //lastKeyFrameLoaded
this._previousFrame = -1
this.meshDataSize = undefined
this.keyFrame = undefined
this._verticesSize = undefined
/** @type {Float32Array} */
this.verticesData = null
this.normalsData = null
/** @type {Uint16Array} */
this.indicesData = null
/** @type {Float32Array} */
this.uvsData = null
this.textureData = null
// ptr to frame start and frameData (vertices)
this.framesDirectory = {}
this.onProgress = onProgress
}
isKeyFrame() {
return this.keyFrame === 1 || this.keyFrame === 2
}
async fetch() {
let response = await fetchOnProgress(this._url, this.onProgress)
this.reader = new BinaryReader(response.response)
this.createFrameDictionary()
}
createFrameDictionary() {
let keyFrameNumber = null
for (let i = 0; i < this.header.frameCount; i++) {
let cur = this.reader.cur
this.readNextFrame(false)
if (this.isKeyFrame()) {
keyFrameNumber = this.frameNumber
}
this.framesDirectory[this.frameNumber] = {
cur,
frameNumber: this.frameNumber,
meshDataSize: this.meshDataSize,
keyFrame: this.keyFrame,
keyFrameNumber
}
}
}
/**
* Read whatever frame and takes care that previous keyframe was loaded
* @param frameNum
*/
readSeekFrame(frameNum) {
const frameDirectory = this.framesDirectory[frameNum]
if(!frameDirectory) {
console.error(`frame ${frameNum} not found in `, this.framesDirectory, '. Will skip that frame. Check the configured fps, if video has same number of frames than sequences file, etc')
return
}
// if keyframe not loaded
if(frameDirectory.keyFrame === 0 && frameDirectory.keyFrameNumber !== this.lastKeyFrameNumber) {
//prev version, was more optimized because was loading only this.readFrameData() not unnecessary data such as vertices, but cleaner code now. And normally skipped frame should not happen often
const keyframeDirectory = this.framesDirectory[frameDirectory.keyFrameNumber]
this.reader.cur = keyframeDirectory.cur //put pointer to the closest previous keyframe
this.readNextFrame(true)
}
this.reader.cur = frameDirectory.cur //put pointer to frameNum frame
this.readNextFrame(true)
}
/**
* Read the next frame, next according to reader.cur (ptr)
* @param readFrameData read the frame data or skip them (useful to optimize the creation of the dictionary)
*/
readNextFrame(readFrameData = true) {
this.frameNumber = this.reader.readInt32()
this.meshDataSize = this.reader.readInt32()
this.keyFrame = this.reader.readByte()
this.frameHeaderCheck()
if(readFrameData) {
this.readFrameData()
} else {
this.skipFrameData()
}
}
skipFrameData() {
this.reader.cur += this.meshDataSize + 4 //where is the missing float32?!?
}
////VologramFrame.cs:ParseBody
readFrameData() {
this.readVerticesData()
if (this.header.hasNormal())
this.readNormalsData()
// New UVs from that frame
if (this.isKeyFrame()) {
this.lastKeyFrameNumber = this.frameNumber
this.readIndicesData()
this.readUvsData()
}
if (this.header.isTextured())
this.readTextureData()
this._frameMeshDataSize = this.reader.readInt32()
this.frameDataSizeCheck()
}
readVerticesData() {
this._verticesSize = this.reader.readInt32()
this.verticesData = this.reader.readArray(this._verticesSize, TypedArrays.float32) // read Vector3 - 3 float - 3x4=12 byte each Vector3
}
readNormalsData() {
this._normalSize = this.reader.readInt32()
if (this._normalSize <= 0) throw new Error(`Invalid normals length value (${this._normalSize})`)
if (this._normalSize !== this._verticesSize) throw new Error(`The number of normals (size:${this._normalSize}) does not match the number of vertices (size:${this._verticesSize}`)
this.normalsData = this.reader.readArray(this._normalSize, TypedArrays.float32) // nb items : {x,y,z} * (this._normalSize / 12)
}
readIndicesData() {
this._indicesSize = this.reader.readInt32()
if (this._indicesSize <= 0) throw new Error(`Invalid indices length value (${this._indicesSize})`)
this.indicesData = null
let verticesCount = this._verticesSize / 4
if (verticesCount / 3 < 65535) {
this.usingShortIndices = true
this.indicesData = this.reader.readArray(this._indicesSize, TypedArrays.uint16) //2=SIZE_C_SHORT
} else {
this.usingShortIndices = false
this.indicesData = this.reader.readArray(this._indicesSize, TypedArrays.uint32) //4=SIZE_C_INT
}
}
readUvsData() {
this._uvsSize = this.reader.readInt32() //size in bytes
if (this._uvsSize <= 0) throw new Error("Invalid uvs length value (" + this._uvsSize + ")")
if (this._uvsSize / 2 !== this._verticesSize / 3) throw new Error(`The number of UVs does not match the number of vertices: ${this._uvsSize}(_uvsSize)/2 !== ${this._verticesSize}(_verticesSize)/3`)
this.uvsData = this.reader.readArray(this._uvsSize, TypedArrays.float32)
}
readTextureData() { //TODO try it, current vols file; do not contains texture
this._textureSize = this.reader.readInt32()
this.textureData = []
if (this._textureSize <= 0) throw new Error(`Invalid texture size value (${this._textureSize})`)
this.textureData = this.reader.readArray(this._textureSize, TypedArrays.uint8)
}
frameHeaderCheck() {
if (this.frameNumber < 0) throw new Error(`Invalid frameNumber (${this.frameNumber})`)
if (this.meshDataSize < 1) throw new Error(`Invalid meshDataSize (${this.meshDataSize})`)
if (this.keyFrame > 2) throw new Error(`Invalid keyFrame (${this.keyFrame})`)
}
frameDataSizeCheck() {
if (this._frameMeshDataSize !== this.meshDataSize)
throw new Error(`Total size before ${this.meshDataSize} and after ${this._frameMeshDataSize} body do not match`)
}
}