///|
/// Safe binary access for glTF buffer data.
///
/// The parser intentionally keeps buffer bytes separate from the JSON
/// document. This module is the production boundary between those two
/// representations: every read checks the byte window before touching the
/// input, and every accessor decode preserves the declared stride and
/// component type.
///|
pub(all) suberror BinaryError {
NegativeOffset(offset~ : Int)
InvalidWidth(width~ : Int)
OutOfBounds(offset~ : Int, width~ : Int, length~ : Int)
InvalidStride(stride~ : Int, element_size~ : Int)
MissingBuffer(index~ : Int)
MissingBufferView(index~ : Int)
InvalidAccessor(index~ : Int)
UnsupportedSparseAccessor(index~ : Int)
InvalidComponentValue(message~ : String)
} derive(Debug, Eq)
///|
pub impl Show for BinaryError with fn output(self, logger) {
match self {
NegativeOffset(offset~) =>
logger.write_string("BinaryError: negative offset \{offset}")
InvalidWidth(width~) =>
logger.write_string("BinaryError: invalid read width \{width}")
OutOfBounds(offset~, width~, length~) =>
logger.write_string(
"BinaryError: range \{offset}+\{width} exceeds \{length}",
)
InvalidStride(stride~, element_size~) =>
logger.write_string(
"BinaryError: stride \{stride} is smaller than element \{element_size}",
)
MissingBuffer(index~) =>
logger.write_string("BinaryError: missing buffer \{index}")
MissingBufferView(index~) =>
logger.write_string("BinaryError: missing bufferView \{index}")
InvalidAccessor(index~) =>
logger.write_string("BinaryError: invalid accessor \{index}")
UnsupportedSparseAccessor(index~) =>
logger.write_string(
"BinaryError: sparse accessor \{index} is not decoded",
)
InvalidComponentValue(message~) =>
logger.write_string("BinaryError: \{message}")
}
}
///|
pub(all) struct BinaryRange {
offset : Int
length : Int
} derive(Debug, ToJson, Eq)
///|
pub(all) struct Vec4 {
x : Double
y : Double
z : Double
w : Double
} derive(Debug, ToJson, Eq)
///|
pub(all) struct BinaryBuffer {
declared_length : Int
bytes : Bytes
source : BufferSource
} derive(Debug, ToJson, Eq)
///|
pub(all) enum BufferSource {
Embedded
GlbBin
External
Missing
} derive(Debug, ToJson, Eq)
///|
pub(all) struct BinaryAsset {
buffers : Array[BinaryBuffer]
glb_bin : Bytes?
} derive(Debug, ToJson, Eq)
///|
pub(all) enum AccessorDecodeMode {
Raw
Normalized
} derive(Debug, ToJson, Eq)
///|
pub(all) struct DecodedAccessor {
accessor_index : Int
count : Int
component_count : Int
values : Array[Double]
valid : Bool
errors : Array[String]
} derive(Debug, ToJson, Eq)
///|
pub(all) struct DecodedVector {
values : Array[Vec4]
component_count : Int
valid : Bool
errors : Array[String]
} derive(Debug, ToJson, Eq)
///|
pub fn binary_range(offset : Int, length : Int) -> BinaryRange {
{ offset, length }
}
///|
pub fn binary_buffer(
bytes : Bytes,
declared_length~ : Int,
source~ : BufferSource,
) -> BinaryBuffer {
{ declared_length, bytes, source }
}
///|
pub fn binary_asset(buffers : Array[BinaryBuffer]) -> BinaryAsset {
{ buffers, glb_bin: None }
}
///|
pub fn binary_asset_with_glb(
buffers : Array[BinaryBuffer],
glb_bin : Bytes,
) -> BinaryAsset {
{ buffers, glb_bin: Some(glb_bin) }
}
///|
pub fn binary_asset_empty() -> BinaryAsset {
{ buffers: [], glb_bin: None }
}
///|
pub fn binary_buffer_count(asset : BinaryAsset) -> Int {
asset.buffers.length()
}
///|
pub fn binary_buffer_length(asset : BinaryAsset, index : Int) -> Int {
match asset.buffers.get(index) {
Some(buffer) => buffer.bytes.length()
None => 0
}
}
///|
pub fn binary_buffer_declared_length(asset : BinaryAsset, index : Int) -> Int {
match asset.buffers.get(index) {
Some(buffer) => buffer.declared_length
None => 0
}
}
///|
pub fn binary_buffer_source(asset : BinaryAsset, index : Int) -> BufferSource {
match asset.buffers.get(index) {
Some(buffer) => buffer.source
None => Missing
}
}
///|
pub fn binary_buffer_complete(asset : BinaryAsset, index : Int) -> Bool {
match asset.buffers.get(index) {
Some(buffer) =>
buffer.declared_length >= 0 &&
buffer.bytes.length() >= buffer.declared_length
None => false
}
}
///|
pub fn binary_asset_declared_bytes(asset : BinaryAsset) -> Int {
asset.buffers.fold(init=0, (sum, buffer) => sum + buffer.declared_length)
}
///|
pub fn binary_asset_loaded_bytes(asset : BinaryAsset) -> Int {
asset.buffers.fold(init=0, (sum, buffer) => sum + buffer.bytes.length())
}
///|
pub fn binary_asset_missing_count(asset : BinaryAsset) -> Int {
asset.buffers.fold(init=0, (count, buffer) => {
match buffer.source {
Missing => count + 1
_ => count
}
})
}
///|
fn ensure_range(
data : Bytes,
offset : Int,
width : Int,
) -> Unit raise BinaryError {
if offset < 0 {
raise BinaryError::NegativeOffset(offset~)
}
if width <= 0 {
raise BinaryError::InvalidWidth(width~)
}
if offset > data.length() || width > data.length() - offset {
raise BinaryError::OutOfBounds(offset~, width~, length=data.length())
}
}
///|
pub fn binary_range_is_valid(data : Bytes, range : BinaryRange) -> Bool {
if range.offset < 0 || range.length < 0 {
false
} else {
range.offset <= data.length() &&
range.length <= data.length() - range.offset
}
}
///|
pub fn read_u8(data : Bytes, offset : Int) -> Int raise BinaryError {
ensure_range(data, offset, 1)
data[offset].to_int()
}
///|
pub fn read_i8(data : Bytes, offset : Int) -> Int raise BinaryError {
let value = read_u8(data, offset)
if value >= 128 {
value - 256
} else {
value
}
}
///|
pub fn read_u16_le(data : Bytes, offset : Int) -> Int raise BinaryError {
ensure_range(data, offset, 2)
data[offset].to_int() | (data[offset + 1].to_int() << 8)
}
///|
pub fn read_u16_be(data : Bytes, offset : Int) -> Int raise BinaryError {
ensure_range(data, offset, 2)
(data[offset].to_int() << 8) | data[offset + 1].to_int()
}
///|
pub fn read_i16_le(data : Bytes, offset : Int) -> Int raise BinaryError {
let value = read_u16_le(data, offset)
if value >= 32768 {
value - 65536
} else {
value
}
}
///|
pub fn read_i16_be(data : Bytes, offset : Int) -> Int raise BinaryError {
let value = read_u16_be(data, offset)
if value >= 32768 {
value - 65536
} else {
value
}
}
///|
pub fn read_u32_le(data : Bytes, offset : Int) -> UInt raise BinaryError {
ensure_range(data, offset, 4)
let b0 = data[offset].to_uint()
let b1 = data[offset + 1].to_uint()
let b2 = data[offset + 2].to_uint()
let b3 = data[offset + 3].to_uint()
b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
}
///|
pub fn read_u32_be(data : Bytes, offset : Int) -> UInt raise BinaryError {
ensure_range(data, offset, 4)
let b0 = data[offset].to_uint()
let b1 = data[offset + 1].to_uint()
let b2 = data[offset + 2].to_uint()
let b3 = data[offset + 3].to_uint()
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
}
///|
pub fn read_i32_le(data : Bytes, offset : Int) -> Int raise BinaryError {
read_u32_le(data, offset).reinterpret_as_int()
}
///|
pub fn read_i32_be(data : Bytes, offset : Int) -> Int raise BinaryError {
read_u32_be(data, offset).reinterpret_as_int()
}
///|
pub fn read_f32_le(data : Bytes, offset : Int) -> Double raise BinaryError {
Float::reinterpret_from_uint(read_u32_le(data, offset)).to_double()
}
///|
pub fn read_f32_be(data : Bytes, offset : Int) -> Double raise BinaryError {
Float::reinterpret_from_uint(read_u32_be(data, offset)).to_double()
}
///|
pub fn read_bytes(
data : Bytes,
offset : Int,
length : Int,
) -> Bytes raise BinaryError {
ensure_range(data, offset, if length == 0 { 1 } else { length })
if length == 0 {
Bytes::from_array([])
} else {
data.view(start=offset, end=offset + length).to_owned()
}
}
///|
pub fn binary_checksum(data : Bytes) -> UInt {
let mut hash : UInt = 2166136261
for byte in data {
hash = (hash ^ byte.to_uint()) * 16777619
}
hash
}
///|
pub fn binary_checksum_range(
data : Bytes,
offset : Int,
length : Int,
) -> UInt raise BinaryError {
ensure_range(data, offset, if length == 0 { 1 } else { length })
let mut hash : UInt = 2166136261
if length > 0 {
for i in offset..<(offset + length) {
hash = (hash ^ data[i].to_uint()) * 16777619
}
}
hash
}
///|
pub fn binary_compare_ranges(
left : Bytes,
left_offset : Int,
right : Bytes,
right_offset : Int,
length : Int,
) -> Bool {
if left_offset < 0 || right_offset < 0 || length < 0 {
false
} else if left_offset > left.length() ||
right_offset > right.length() ||
length > left.length() - left_offset ||
length > right.length() - right_offset {
false
} else {
let mut equal = true
for i in 0.. Int? {
match doc.accessors.get(accessor_index) {
Some(accessor) =>
match accessor.buffer_view {
Some(view_index) =>
match doc.buffer_views.get(view_index) {
Some(view) => Some(view.buffer)
None => None
}
None => None
}
None => None
}
}
///|
pub fn accessor_byte_range(
doc : GltfDocument,
accessor_index : Int,
) -> BinaryRange? {
if accessor_index < 0 || accessor_index >= doc.accessors.length() {
None
} else {
let layout = accessor_layout(doc, accessor_index)
Some({
offset: layout.first_byte,
length: if layout.valid {
layout.last_byte_exclusive - layout.first_byte
} else {
0
},
})
}
}
///|
pub fn binary_asset_for_document(
doc : GltfDocument,
buffers : Array[Bytes],
) -> BinaryAsset {
let loaded : Array[BinaryBuffer] = []
for index in 0.. value
None => Bytes::from_array([])
}
let source = if bytes.length() == 0 && declared > 0 {
Missing
} else if doc.buffers[index].uri is Some(_) {
External
} else {
Embedded
}
loaded.push({ declared_length: declared, bytes, source })
}
{ buffers: loaded, glb_bin: None }
}
///|
pub fn binary_asset_for_glb(
doc : GltfDocument,
buffers : Array[Bytes],
bin : Bytes,
) -> BinaryAsset {
let asset = binary_asset_for_document(doc, buffers)
{ ..asset, glb_bin: Some(bin) }
}
///|
fn buffer_for_accessor(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> (Bytes, Int, Int, Int)? {
let accessor = doc.accessors.get(accessor_index)
match accessor {
None => None
Some(value) =>
match value.buffer_view {
None => None
Some(view_index) =>
match doc.buffer_views.get(view_index) {
None => None
Some(view) =>
match asset.buffers.get(view.buffer) {
None => None
Some(buffer) => {
let stride = match view.byte_stride {
Some(value) => value
None => accessor_element_size(value)
}
Some(
(
buffer.bytes,
view.byte_offset + value.byte_offset,
stride,
view.byte_length,
),
)
}
}
}
}
}
}
///|
fn normalized_component(component : ComponentType, value : Double) -> Double {
match component {
Byte => if value / 127.0 < -1.0 { -1.0 } else { value / 127.0 }
UnsignedByte => value / 255.0
Short => if value / 32767.0 < -1.0 { -1.0 } else { value / 32767.0 }
UnsignedShort => value / 65535.0
UnsignedInt => value / 4294967295.0
Float => value
}
}
///|
fn read_component(
data : Bytes,
component : ComponentType,
offset : Int,
mode : AccessorDecodeMode,
) -> Double raise BinaryError {
let value = match component {
Byte => read_i8(data, offset).to_double()
UnsignedByte => read_u8(data, offset).to_double()
Short => read_i16_le(data, offset).to_double()
UnsignedShort => read_u16_le(data, offset).to_double()
UnsignedInt => read_u32_le(data, offset).to_double()
Float => read_f32_le(data, offset)
}
match mode {
Raw => value
Normalized => normalized_component(component, value)
}
}
///|
fn read_component_or_none(
data : Bytes,
component : ComponentType,
offset : Int,
mode : AccessorDecodeMode,
) -> Double? {
Some(read_component(data, component, offset, mode)) catch {
_ => None
}
}
///|
fn read_component_width(component : ComponentType) -> Int {
component_info(component).size
}
///|
fn decode_error(
accessor_index : Int,
count : Int,
component_count : Int,
message : String,
) -> DecodedAccessor {
{
accessor_index,
count,
component_count,
values: [],
valid: false,
errors: [message],
}
}
///|
pub fn decode_accessor(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> DecodedAccessor {
decode_accessor_with_mode(doc, asset, accessor_index, Raw)
}
///|
pub fn decode_accessor_normalized(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> DecodedAccessor {
decode_accessor_with_mode(doc, asset, accessor_index, Normalized)
}
///|
pub fn decode_accessor_with_mode(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
mode : AccessorDecodeMode,
) -> DecodedAccessor {
match doc.accessors.get(accessor_index) {
None => decode_error(accessor_index, 0, 0, "accessor index is invalid")
Some(accessor) => {
let component_count = accessor_component_count(accessor.accessor_type)
if accessor.count < 0 {
decode_error(
accessor_index,
accessor.count,
component_count,
"accessor count cannot be negative",
)
} else {
match buffer_for_accessor(doc, asset, accessor_index) {
None =>
if accessor.count == 0 {
{
accessor_index,
count: 0,
component_count,
values: [],
valid: true,
errors: [],
}
} else {
decode_error(
accessor_index,
accessor.count,
component_count,
"accessor has no loaded bufferView",
)
}
Some((data, start, stride, view_length)) => {
let width = read_component_width(accessor.component_type)
let element_size = width * component_count
if stride < element_size {
decode_error(
accessor_index,
accessor.count,
component_count,
"bufferView stride is smaller than the accessor element",
)
} else if start < 0 || start > data.length() {
decode_error(
accessor_index,
accessor.count,
component_count,
"accessor start is outside the loaded buffer",
)
} else {
let needed = if accessor.count == 0 {
0
} else {
stride * (accessor.count - 1) + element_size
}
if needed > view_length || needed > data.length() - start {
decode_error(
accessor_index,
accessor.count,
component_count,
"accessor data exceeds the loaded buffer window",
)
} else {
let values : Array[Double] = []
let mut failed : String? = None
for element in 0.. values.push(value)
None => failed = Some("component read failed")
}
}
}
match failed {
Some(message) =>
decode_error(
accessor_index,
accessor.count,
component_count,
message,
)
None =>
{
accessor_index,
count: accessor.count,
component_count,
values,
valid: true,
errors: [],
}
}
}
}
}
}
}
}
}
}
///|
pub fn decoded_accessor_value(
decoded : DecodedAccessor,
element : Int,
component : Int,
) -> Double? {
if element < 0 ||
element >= decoded.count ||
component < 0 ||
component >= decoded.component_count {
None
} else {
decoded.values.get(element * decoded.component_count + component)
}
}
///|
pub fn decoded_accessor_element(
decoded : DecodedAccessor,
element : Int,
) -> Array[Double] {
if element < 0 || element >= decoded.count {
[]
} else {
let start = element * decoded.component_count
let values : Array[Double] = []
for index in 0.. values.push(value)
None => ()
}
}
values
}
}
///|
pub fn decoded_accessor_min(decoded : DecodedAccessor) -> Double? {
decoded.values.fold(init=None, (current, value) => {
match current {
None => Some(value)
Some(previous) => Some(if value < previous { value } else { previous })
}
})
}
///|
pub fn decoded_accessor_max(decoded : DecodedAccessor) -> Double? {
decoded.values.fold(init=None, (current, value) => {
match current {
None => Some(value)
Some(previous) => Some(if value > previous { value } else { previous })
}
})
}
///|
pub fn decoded_accessor_average(decoded : DecodedAccessor) -> Double? {
if decoded.values.length() == 0 {
None
} else {
Some(
decoded.values.fold(init=0.0, (sum, value) => sum + value) /
decoded.values.length().to_double(),
)
}
}
///|
pub fn decode_accessor_vectors(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> DecodedVector {
let decoded = decode_accessor_normalized(doc, asset, accessor_index)
if !decoded.valid {
{
values: [],
component_count: decoded.component_count,
valid: false,
errors: decoded.errors,
}
} else {
let values : Array[Vec4] = []
for index in 0.. value
None => 0.0
}
let y = match item.get(1) {
Some(value) => value
None => 0.0
}
let z = match item.get(2) {
Some(value) => value
None => 0.0
}
let w = match item.get(3) {
Some(value) => value
None => 1.0
}
values.push({ x, y, z, w })
}
{
values,
component_count: decoded.component_count,
valid: true,
errors: [],
}
}
}
///|
pub fn decode_accessor_scalars(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> Array[Double] {
let decoded = decode_accessor(doc, asset, accessor_index)
if decoded.component_count != 1 || !decoded.valid {
[]
} else {
decoded.values
}
}
///|
pub fn decode_index_values(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> Array[Int] {
match doc.accessors.get(accessor_index) {
None => []
Some(accessor) =>
if !accessor_is_index_compatible(accessor) {
[]
} else {
decode_accessor(doc, asset, accessor_index).values.map(value => {
value.to_int()
})
}
}
}
///|
pub fn decode_position_values(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> Array[Vec3] {
let decoded = decode_accessor_normalized(doc, asset, accessor_index)
if !decoded.valid || decoded.component_count < 3 {
[]
} else {
let values : Array[Vec3] = []
for index in 0..= 3 {
values.push({ x: item[0], y: item[1], z: item[2] })
}
}
values
}
}
///|
pub fn decode_texcoord_values(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> Array[Vec2] {
let decoded = decode_accessor_normalized(doc, asset, accessor_index)
if !decoded.valid || decoded.component_count < 2 {
[]
} else {
let values : Array[Vec2] = []
for index in 0..= 2 {
values.push({ x: item[0], y: item[1] })
}
}
values
}
}
///|
pub fn decode_normal_values(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> Array[Vec3] {
decode_position_values(doc, asset, accessor_index)
}
///|
pub fn decode_quaternion_values(
doc : GltfDocument,
asset : BinaryAsset,
accessor_index : Int,
) -> Array[Quat] {
let decoded = decode_accessor_normalized(doc, asset, accessor_index)
if !decoded.valid || decoded.component_count < 4 {
[]
} else {
let values : Array[Quat] = []
for index in 0..= 4 {
values.push({ x: item[0], y: item[1], z: item[2], w: item[3] })
}
}
values
}
}
///|
pub fn binary_asset_validate(
doc : GltfDocument,
asset : BinaryAsset,
) -> Array[String] {
let errors : Array[String] = []
if asset.buffers.length() != doc.buffers.length() {
errors.push(
"loaded buffer count \{asset.buffers.length()} does not match document count \{doc.buffers.length()}",
)
}
for index in 0.. errors.push("missing loaded buffer \{index}")
Some(buffer) =>
if buffer.declared_length < 0 {
errors.push("buffer \{index} has negative declared length")
} else if buffer.bytes.length() < buffer.declared_length &&
buffer.source != Missing {
errors.push("buffer \{index} is shorter than declared")
}
}
}
for index in 0.. Array[DecodedAccessor] {
indices.map(index => decode_accessor(doc, asset, index))
}
///|
pub fn decode_all_accessors(
doc : GltfDocument,
asset : BinaryAsset,
) -> Array[DecodedAccessor] {
let decoded : Array[DecodedAccessor] = []
for index in 0.. Bool {
for value in decoded.values {
if value != value || value == 1.0 / 0.0 || value == -1.0 / 0.0 {
return true
}
}
false
}
///|
pub fn decoded_accessor_component_bounds(
decoded : DecodedAccessor,
component : Int,
) -> (Double, Double)? {
if component < 0 || component >= decoded.component_count || decoded.count == 0 {
None
} else {
let first = decoded.values[component]
let mut low = first
let mut high = first
for element in 1.. high {
high = value
}
}
Some((low, high))
}
}
///|
pub fn decoded_accessor_is_monotonic(decoded : DecodedAccessor) -> Bool {
if decoded.component_count != 1 {
false
} else {
let mut previous : Double? = None
let mut result = true
for value in decoded.values {
match previous {
Some(old) => if value < old { result = false }
None => ()
}
previous = Some(value)
}
result
}
}
///|
pub fn decoded_accessor_duplicate_count(decoded : DecodedAccessor) -> Int {
if decoded.component_count == 0 {
0
} else {
let mut duplicates = 0
for index in 1..