///|
pub(all) enum AttributeValidationSeverity {
AttributeInfo
AttributeWarning
AttributeError
} derive(Debug, ToJson, Eq)
///|
pub(all) struct AttributeValidationIssue {
attribute : String
severity : AttributeValidationSeverity
code : String
message : String
index : Int?
} derive(Debug, ToJson, Eq)
///|
pub(all) struct AttributeValidationReport {
vertex_count : Int
attributes : Array[String]
issues : Array[AttributeValidationIssue]
valid : Bool
complete : Bool
score : Double
} derive(Debug, ToJson, Eq)
///|
pub(all) struct AttributeValidationPolicy {
require_position : Bool
require_consistent_count : Bool
require_finite : Bool
allow_unknown : Bool
max_texcoord_set : Int
} derive(Debug, ToJson, Eq)
///|
pub fn attribute_validation_severity_name(
value : AttributeValidationSeverity,
) -> String {
match value {
AttributeInfo => "info"
AttributeWarning => "warning"
AttributeError => "error"
}
}
///|
pub fn default_attribute_validation_policy() -> AttributeValidationPolicy {
{
require_position: true,
require_consistent_count: true,
require_finite: true,
allow_unknown: true,
max_texcoord_set: 8,
}
}
///|
pub fn attribute_issue(
attribute : String,
severity : AttributeValidationSeverity,
code : String,
message : String,
index : Int?,
) -> AttributeValidationIssue {
{ attribute, severity, code, message, index }
}
///|
pub fn attribute_name_is_known(name : String) -> Bool {
name == "POSITION" ||
name == "NORMAL" ||
name == "TANGENT" ||
name == "COLOR_0" ||
name.has_prefix("TEXCOORD_") ||
name.has_prefix("JOINTS_") ||
name.has_prefix("WEIGHTS_")
}
///|
pub fn attribute_name_is_texcoord(name : String) -> Bool {
name.has_prefix("TEXCOORD_")
}
///|
pub fn attribute_name_set(name : String) -> Int? {
match name {
"TEXCOORD_0" => Some(0)
"TEXCOORD_1" => Some(1)
"TEXCOORD_2" => Some(2)
"TEXCOORD_3" => Some(3)
"TEXCOORD_4" => Some(4)
"TEXCOORD_5" => Some(5)
"TEXCOORD_6" => Some(6)
"TEXCOORD_7" => Some(7)
"TEXCOORD_8" => Some(8)
_ => None
}
}
///|
pub fn attribute_name_is_finite(name : String) -> Bool {
name.length() > 0 && name != "POSITION_"
}
///|
pub fn attribute_names_sorted(attributes : Map[String, Int]) -> Array[String] {
let result : Array[String] = []
for name, _ in attributes {
result.push(name)
}
for pass in 0.. result[index + 1] {
let old = result[index]
result[index] = result[index + 1]
result[index + 1] = old
}
}
}
result
}
///|
pub fn attribute_count_map(
attributes : Map[String, Int],
accessors : Array[GltfAccessor],
) -> Map[String, Int] {
let result : Map[String, Int] = Map([])
for name, accessor_index in attributes {
match accessors.get(accessor_index) {
Some(accessor) => result[name] = accessor.count
None => result[name] = -1
}
}
result
}
///|
pub fn attribute_validation_report(
attributes : Map[String, Int],
accessors : Array[GltfAccessor],
policy : AttributeValidationPolicy,
) -> AttributeValidationReport {
let names = attribute_names_sorted(attributes)
let counts = attribute_count_map(attributes, accessors)
let issues : Array[AttributeValidationIssue] = []
let vertex_count = match counts.get("POSITION") {
Some(value) => value
None => 0
}
if policy.require_position && !attributes.contains("POSITION") {
issues.push(
attribute_issue(
"POSITION",
AttributeError,
"missing-position",
"POSITION attribute is required",
None,
),
)
}
for name in names {
match counts.get(name) {
None =>
issues.push(
attribute_issue(
name,
AttributeError,
"missing-accessor",
"attribute accessor is missing",
None,
),
)
Some(count) => {
if count < 0 {
issues.push(
attribute_issue(
name,
AttributeError,
"invalid-accessor",
"attribute accessor index is invalid",
None,
),
)
}
if policy.require_consistent_count &&
vertex_count > 0 &&
count >= 0 &&
count != vertex_count {
issues.push(
attribute_issue(
name,
AttributeError,
"count-mismatch",
"attribute count differs from POSITION",
None,
),
)
}
match attribute_name_set(name) {
Some(set) =>
if set > policy.max_texcoord_set {
issues.push(
attribute_issue(
name,
AttributeWarning,
"texcoord-set",
"texture coordinate set is above policy",
Some(set),
),
)
}
None => ()
}
if !policy.allow_unknown && !attribute_name_is_known(name) {
issues.push(
attribute_issue(
name,
AttributeWarning,
"unknown-attribute",
"attribute name is not in the core set",
None,
),
)
}
}
}
}
let error_count = issues
.filter(fn(issue) { issue.severity == AttributeError })
.length()
let warning_count = issues
.filter(fn(issue) { issue.severity == AttributeWarning })
.length()
let score = 100.0 -
Double::from_int(error_count) * 25.0 -
Double::from_int(warning_count) * 5.0
{
vertex_count,
attributes: names,
issues,
valid: error_count == 0,
complete: attributes.contains("POSITION") && error_count == 0,
score: if score < 0.0 {
0.0
} else {
score
},
}
}
///|
pub fn attribute_validation_for_primitive(
primitive : GltfPrimitive,
accessors : Array[GltfAccessor],
policy : AttributeValidationPolicy,
) -> AttributeValidationReport {
attribute_validation_report(primitive.attributes, accessors, policy)
}
///|
pub fn attribute_issue_codes(
report : AttributeValidationReport,
) -> Array[String] {
report.issues.map(fn(issue) { issue.code })
}
///|
pub fn attribute_issues_for_name(
report : AttributeValidationReport,
name : String,
) -> Array[AttributeValidationIssue] {
report.issues.filter(fn(issue) { issue.attribute == name })
}
///|
pub fn attribute_report_has_code(
report : AttributeValidationReport,
code : String,
) -> Bool {
report.issues.any(fn(issue) { issue.code == code })
}
///|
pub fn attribute_report_merge(
left : AttributeValidationReport,
right : AttributeValidationReport,
) -> AttributeValidationReport {
let attributes = left.attributes.copy()
for name in right.attributes {
if !attributes.contains(name) {
attributes.push(name)
}
}
let issues = left.issues.copy()
for issue in right.issues {
issues.push(issue)
}
let errors = issues
.filter(fn(issue) { issue.severity == AttributeError })
.length()
{
vertex_count: if left.vertex_count > right.vertex_count {
left.vertex_count
} else {
right.vertex_count
},
attributes,
issues,
valid: errors == 0,
complete: left.complete && right.complete,
score: (left.score + right.score) * 0.5,
}
}
///|
pub fn attribute_report_lines(
report : AttributeValidationReport,
) -> Array[String] {
[
"vertex_count=\{report.vertex_count}",
"attributes=\{report.attributes.length()}",
"issues=\{report.issues.length()}",
"valid=\{report.valid}",
"complete=\{report.complete}",
"score=\{report.score}",
]
}