Collusion with Rust ยท parsing OFF files

OFF file is a plain text file, which can be used to record three-dimensional (or higher dimensional) polyhedron information. Its basic format is

OFF
 Number of points, number of faces, number of sides
 Point table
 Surface table

for example

OFF
4 4 6
0 0 0
1 0 0
0 1 0
0 0 1
3 0 1 2
3 0 1 3
3 1 2 3
3 0 2 3

The record is a tetrahedron defined by four vertices, with the number of sides of 6. The point table records the coordinate information of the four vertices

0 0 0
1 0 0
0 1 0
0 0 1

The surface table records the information of four patches

3 0 1 2
3 0 1 3
3 1 2 3
3 0 2 3

The number in the first column of the face table indicates how many vertices each patch is composed of, and the subsequent columns are the index numbers of the point table, for example

3 0 1 3

Indicates that the patch is composed of three vertices. The index numbers of these vertices in the point table are 0, 1 and 3 respectively, corresponding to the first point, the second point and the fourth point in the point table.

Note that the number of sides of the OFF file header can be set to 0, which does not affect the correctness of the polyhedron structure.

Mesh structure

Before considering how to parse the OFF file, you need to determine the data type in memory that can be used to express the polyhedron structure, otherwise the parsing work is difficult to implement. This data type can be defined as the structure type:

struct Mesh {
    points: Vec<Vec<f64>>,  // Point table
    facets: Vec<Vec<usize>> // Surface table
}

Among them, the vector (Vec) instances used to represent points and patches are located on the heap, so there is usually no need to worry about stack overflow caused by Mesh instances. Originally, I wanted to define Mesh as

struct Point {
    n: usize, // dimension
    body: Box<[f64]> // coordinate
}

struct Facet {
    n: usize, // Number of vertices
    vertices: Box<[usize]> // Vertex Index 
}

struct Mesh {
    points: Vec<Point>, // Point table
    facets: Vec<Facet>  // Surface table
}

The above definition is logically more consistent with the polyhedron model, but using Box pointer cannot dynamically allocate heap space for Point and Facet. In fact, this problem has appeared in the "n-dimensional Point" section of the "Point".

The following code defines the new and display methods for Mesh:

impl Mesh {
    fn new() -> Mesh {
        return Mesh {points: Vec::new(), facets: Vec::new()};
    }
    
    fn display(&self) {
        println!("OFF");
        println!("{0}, {1}", self.points.len(), self.facets.len());
        for x in &(self.points) {
            let n = x.len();
            for i in 0 .. n {
                if i == n - 1 {
                    println!("{}", x[i]);
                } else {
                    print!("{} ", x[i]);
                }
            }
        }
        for f in &(self.facets) {
            let n = f.len();
            print!("{} ", n);
            for i in 0 .. n {
                if i == n - 1 {
                    println!("{}", f[i]);
                } else {
                    print!("{} ", f[i]);
                }
            }
        }
    }
}

Where & (self. Points) refers to the points member of the Mesh structure, or it can be written as & self Points, because The operator has a higher priority than &. However, why do we need to quote here?

Reference to collection type

For vectors, when using for When traversing it with in syntax, you need to pass the vector instance as a parameter to the iterator. There are three types of parameter transmission:

  • Example of transmission vector;
  • Reference of vector instance;
  • Pass a variable reference to a vector instance.

Since the points in the above code is a member of the Mesh structure, if its ownership is transferred, the Mesh will lose this member. rustc does not allow the definition of the structure to be destroyed so arbitrarily, so an error is reported, and the way of passing reference is allowed.

Another problem is that in the process of traversing vectors, there are codes to access vector elements:

for x in &(self.points) {
    let n = x.len();
    for i in 0 .. n {
        if i == n - 1 {
            println!("{}", x[i]);
        } else {
            print!("{} ", x[i]);
        }
    }
}

Where x is an instance of VEC < f64 > or a reference to it? The answer is the latter. In fact, in the process of traversing vectors, there are three ways to obtain vector elements:

  • When transferring vector instances, the ownership of vector elements is obtained;
  • Pass the reference of vector instance and get the reference of vector element;
  • Pass the variable reference of the vector instance, and get the variable reference of the vector element.

generic paradigm

The process of iterating point set and patch set is almost the same, that is

for x in &(self.points) {
    let n = x.len();
    for i in 0 .. n {
        if i == n - 1 {
            println!("{}", x[i]);
        } else {
            print!("{} ", x[i]);
        }
    }
}
for f in &(self.facets) {
    let n = f.len();
    print!("{} ", n);
    for i in 0 .. n {
        if i == n - 1 {
            println!("{}", f[i]);
        } else {
            print!("{} ", f[i]);
        }
    }
}

Suppose there is no in the output process of the surface table`

print!("{} ", n);

The output process of point table and can be unified into a generic function:

fn display_matrix<T>(v: &Vec<T>) {
    for x in v {
        let n = x.len();
        for i in 0 .. n {
            if i == n - 1 {
                println!("{}", x[i]);
            } else {
                print!("{} ", x[i]);
            }
        }
    }
}

This function is named display_matrix is because a vector whose element is a vector is not a matrix?

Then, you can define the display method of Mesh as

impl Mesh {
    fn display(&self) {
        println!("OFF");
        println!("{0}, {1}", self.points.len(), self.facets.len());
        display_matrix(&self.points);
        display_matrix(&self.facets);
    }
}

As a result, you will be severely beaten by rustc:

error[E0599]: no method named `len` found for reference `&T` in the current scope
error[E0608]: cannot index into a value of type `&T`
error[E0608]: cannot index into a value of type `&T`

The first mistake is that rustc thinks display_ The generic parameter T of matrix has no len method. The last two errors are that rustc thinks that T cannot obtain the value with subscript. In other words, rustc wants me to "prove" that T has both len method and subscript value. Rustc is stupid. It doesn'T know that in my example, T is Vec type. There are len methods that can also obtain values through subscripts.

Prove that all vectors have lengths

The way of proof is based on characteristics. For example, the following is a complete program code, which can prove that T has len method for a generic function:

trait HasLength {
    fn len(&self) -> usize;
}

impl HasLength for Vec<usize> {
    fn len(&self) -> usize {
        return self.len();
    }
}

fn display_vec_len<T: HasLength>(v: &T) {
    println!("{}", v.len());
}

fn main() {
    let mut a: Vec<usize> = Vec::new();
    a.push(1);
    a.push(2);
    display_vec_len(&a);
}

The above code first defines a trail named HasLength, and then implements the trail for VEC < use > type. In the generic function display_vec_len restricts the generic parameter T to a type that implements HasLength Trait.

If display_ vec_ How does the len function apply to VEC < f64 > types? Haslength trail needs to be implemented for VEC < f64 >. So a new question arises. Can trail also be generic? This eliminates the need to define haslength trail for each specific VEC < T > type. have a try:

trait HasLength {
    fn len(&self) -> usize;
}

impl<T> HasLength for Vec<T> {
    fn len(&self) -> usize {
        return self.len();
    }
}

fn display_vec_len<T: HasLength>(v: &T) {
    println!("{}", v.len());
}

fn main() {
    let mut a: Vec<usize> = Vec::new();
    a.push(1);
    a.push(2);
    display_vec_len(&a);
    
    let mut b: Vec<f64> = Vec::new();
    b.push(0.1);
    b.push(0.2);
    b.push(0.3);
    display_vec_len(&b);
}

The results are in line with expectations.

Subscripting Operator

The elements accessing arrays and vectors through subscripts are the syntax sugar of Rust, which is implemented based on STD:: Ops:: index trail defined in the standard library. for example

fn main() {
    let a = vec![1, 2, 3, 4];
    println!("{}", a[2]);
}

Equivalent to

use std::ops::Index;
fn main() {
    let a = vec![1,2,3,4];
    println!("{}", a.index(2));
}

For the following generic functions

fn matrix_index<T>(v: &Vec<T>, i: usize, j: usize) -> I don't know what type to return {
    return &v[i][j];
}

If its calling code is

let a = vec![vec![1, 2], vec![3, 4]];
println!("{}", matrix_index(&a, 0, 1));

Here are two questions. First, how to prove matrix to rustc_ Does the generic parameter T of index support subscript operation? Secondly, matrix_ What is the return type of index? The answer is

fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display
    where <T as Index<usize>>::Output: Sized,
          <T as Index<usize>>::Output: Display {
    return &v[i][j];
}

If you want to know where the answer comes from, you need to try step by step in your mistakes.

First, try to prove that T implements STD:: Ops:: index Trail:

use std::ops::Index;

fn matrix_index<T: Index>(v: &Vec<T>, i: usize, j: usize) -> I don't know what type to return {
    return &v[i][j];
}

However, t rust will report an error and give rectification suggestions:

rror[E0107]: missing generics for trait `Index`
add missing generic argument
  |
  | fn matrix_index<T: Index<Idx>>(v: &Vec<T>, ... ... ...

It seems that index trail is not easy to use. Search the Rust standard library document and find that the definition of index trail is [1]

pub trait Index<Idx> where
    Idx: ?Sized, {
    type Output: ?Sized;
    fn index(&self, index: Idx) -> &Self::Output;
}

Does VEC < T > implement this trail? Achieved, the source code is

impl<T, I: SliceIndex<[T]>, A: Allocator> Index<I> for Vec<T, A> {
    type Output = I::Output;

    #[inline]
    fn index(&self, index: I) -> &Self::Output {
        Index::index(&**self, index)
    }
}

There are many grammars in the above code that I have never seen before. Combined with the suggestions given by rustc, we can only see that the way index trail is used to prove that generic parameters support subscript operation is to provide a type parameter for the trail. For VEC < T > type, the parameter should be sliceindex < [t] >, and the latter is a trail.

Try again:

use std::ops::Index;
use std::slice::SliceIndex;

fn matrix_index<T: Index<SliceIndex<[T]>>>(v: &Vec<T>, i: usize, j: usize) -> I don't know what type to return {
    return &v[i][j];
}

For the above code, rustc gives a warning:

warning: trait objects without an explicit `dyn` are deprecated
  |
  | fn matrix_index<T: Index<SliceIndex<[T]>>>(v: &Vec<T>, ... ... ...
  |                          ^^^^^^^^^^^^^^^

According to the suggestion of rustc, the matrix_ The index is modified to

fn matrix_index<T: Index<dyn SliceIndex<[T]>>>(v: &Vec<T>, i: usize, j: usize) -> I don't know what type to return {
    return &v[i][j];
}

Be realistic. At this time, I don't know the purpose of dyn. Let's listen to rustc first. After this, Amitabha, our rustc can finally say less. Finally, it just said coldly:

error[E0191]: the value of the associated type `Output` (from trait `SliceIndex`) must be specified
  |
  | ...x_index<T: Index<dyn SliceIndex<[T]>>>(v: &Vec<T>, ... ... ...
  |                         ^^^^^^^^^^^^^^^ help: specify the associated type: `SliceIndex<[T], Output = Type>`

Well, try again:

fn matrix_index<T: Index<dyn SliceIndex<[T], Output = usize>>>(v: &Vec<T>, i: usize, j: usize) -> I don't know what type to return {
    return v[i][j];
}

Now give matrix_ These bits and pieces added by index are almost like heavenly books. Even so, rustc not only doesn't stop, but goes crazy. There are so many error messages that serious people can't read them. So I had a flash of inspiration and probably guessed the index <... > What should be filled in here. You should fill in the type that implements sliceindex < [t] > trait. For my requirements, the subscript type should be use. Does use implement sliceindex < [t] > trail? The answer is yes, as evidenced by the source code of the standard library [2]. Accordingly, matrix_index is defined as

fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> I don't know what type to return {
    return &v[i][j];
}

Now, rustc has finally stopped criticizing the generic parameter T and turned its attention to the matrix_ The return value type of index is, and the error message is

error[E0412]: cannot find type `I don't know what type to return` in this scope
    |
    |   ...size, j: usize) -> I don't know what type to return {
    |                         ^^^^^^^^^^^^^^^^^^ help: a trait with a similar name exists: `AsMut

I decided to ignore the help given by rustc because I didn't understand what it was saying. matrix_index is a generic function, and the parameter it accepts is a matrix -- a vector whose elements are vectors. The elements of the matrix are not of a specific type. What I don't know, how can rustc know? In addition, I really can't see that I don't know what type to return, which is similar to the two names AsMut.

However, the above suggestions of rustc still give me some inspiration. The return type of a function can be trail [3]! matrix_ What kind of trail should the index return value be? Due to matrix_ The return result of index is passed to println! of Based on my shallow understanding of Rust, println! The accepted parameters must provide an implementation of Display Trait. Therefore, try matrix_ The return value type of index is set to & impl display -- which implements the reference of Display Trait type -- that is

use std::ops::Index;
use std::fmt::Display;

fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display {
    return &v[i][j];
}

After watching my flash of insight, rustc issued another criticism and suggestion:

error[E0277]: `<T as Index<usize>>::Output` doesn't implement `std::fmt::Display`
  |
  | ...i: usize, j: usize) -> impl Display {
  |                           ^^^^^^^^^^^^ `<T as Index<usize>>::Output` cannot be formatted with the default formatter
  ... ... ...
  | fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> impl Display 
  | where <T as Index<usize>>::Output: std::fmt::Display {
  | ++++++++++++++++++++++++++++++++++++++++++++++++++++

Give it a try:

fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display
where <T as Index<usize>>::Output: Display {
    return &v[i][j];
}

rustc also gives new error reports and suggestions:

error[E0277]: the size for values of type `<T as Index<usize>>::Output` 
cannot be known at compilation time
  |
  | fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) 
  | -> &impl Display
  |    ^^^^^^^^^^^^^ doesn't hav  a size known at compile-time
  = help: the trait `Sized` is not implemented for `<T as Index<usize>>::Output`
  ... ... ...
help: consider further restricting the associated type
  |
  | where <T as Index<usize>>::Output: Display, <T as Index<usize>>::Output: Sized {
  |                                           ++++++++++++++++++++++++++++++++++++

Therefore, there is the answer at the beginning of this section. So far, I really don't know how to praise rustc. Perhaps it has always been a world-class problem to prove what is what. In life, please remember not to prove yourself, or your life will be as cumbersome as Rust code. However, there is another possibility. What better way for Rust to write the code at the beginning of this section more gracefully, but I don't know. Now I can only hope so.

unnecessary and overelaborate formalities

Now we can write the display function of vector (matrix) which can almost unify various elements into vectors,

use std::ops::Index;
use std::fmt::Display;

trait HasLength {
    fn len(&self) -> usize;
}

impl<T> HasLength for Vec<T> {
    fn len(&self) -> usize {
        return self.len();
    }
}

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>)
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    for x in v {
        let n = x.len();
        for i in 0 .. n {
            if i == n - 1 {
                println!("{}", x[i]);
            } else {
                print!("{} ", x[i]);
            }
        }
    }
}

impl Mesh {
    fn display(&self) {
        println!("OFF");
        println!("{0}, {1}", self.points.len(), self.facets.len());
        display_matrix(&self.points);
        display_matrix(&self.facets);
    }
}

I feel that the amount of code is more than the code used by the display method that did not unify the traversal process of point table and surface table at the beginning, so... I just want to study trail and generics a little more deeply.

Display

Now that you know something about trail, it's better to implement display trail for Mesh than display trail for Mesh. If you follow the method in [4], you will soon find that you can't write, because Mesh is a composite structure and needs to be written multiple times!, However, all the examples that can be found to implement display are single execution write!. A feasible solution is to collect all the information to be displayed with a String of type String, and then write the String!.

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points).as_str();
        s += display_matrix(&self.facets).as_str();
        write!(f, "{}", s)
    }
}

The key point to be drawn here is the connection and difference between & STR and String. These I have in rhamal Pdf [5].

closure

Please note that when trying to unify the output process of point table and surface table with generic functions, the premise is that the output process of surface table and point table is exactly the same, but in fact it is not the same. When outputting face information, not only the value of the vector in the face table (the subscript of the patch vertex in the point table), but also the number of patch vertices should be output. This difference can be achieved by setting display_matrix is eliminated by adding a parameter. for example

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
                                               prefix: Type to be determined) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        s += prefix(x).as_str();
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

prefix means to add some information before each point or patch information output, but it is worth exploring what type it should be, and this may be a good opportunity to learn more about Rust.

First, what happens if prefix is set to bool type? have a try

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>, prefix: bool) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        if prefix {
            s += format!("{} ", n).as_str();
        }
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

Accordingly, the implementation of Mesh's Display Trait needs to be modified to

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points, false).as_str();
        s += display_matrix(&self.facets, true).as_str();
        write!(f, "{}", s)
    }
}

The above implementation can solve the problem, but the code semantics is not good and the flexibility is too poor. Imagine if the information to be output before the output patch information is not the number of patch vertices, but by display_ What other information does the user of the matrix function decide? A better way is to use the function type as the type of prefix, that is

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
                                               prefix: impl Fn(&T) -> String) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        s += prefix(x).as_str();
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

Then use the closure as the display_ Value of matrix parameter prefix:

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points, |_| "".to_string()).as_str();
        s += display_matrix(&self.facets, |x| format!("{} ", x.len())).as_str();
        write!(f, "{}", s)
    }
}

The above code can work, but it is not efficient. Because when outputting the point table, display_ The matrix function does not need to execute the prefix function, but now it has to be executed, and the executed prefix returns an empty string. How to make display when outputting a point table_ Matrix knows that prefix is null and needs to be ignored? Just make the prefix function have a state. A feasible scheme is given below.

First, define a generic structure:

struct Prefix<T> {
    status: bool,
    body: fn(&T) -> String
}

impl<T> Prefix<T> {
    fn new() -> Prefix<T> {
        Prefix{status: false, body: |_| "".to_string()}
    }
}

Then display_matrix rewritten as

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
                                               prefix: &Prefix<T>) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        if prefix.status {
            s += (prefix.body)(x).as_str();
        }
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

Finally, the Display implementation of Mesh is rewritten as

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points, &Prefix::new()).as_str();
        s += display_matrix(&self.facets, &Prefix{status: true,
                                                  body: |x| format!("{} ", x.len())}).as_str();
        write!(f, "{}", s)
    }
}

OFF file - > mesh structure

After several big episodes, we can return to the subject now. The following code can open the OFF file,

use std::path::Path;
use std::fs::File;

let path = Path::new("foo.off");
let file = File::open(path).unwrap();

When you see unwrap, you should think that the related type may be Result or Option. The Rust standard library is always worried that the return value of a function is too direct and will lead to the destruction of the world. Therefore, it is very keen to ban the return value of a function first and then release it as appropriate.

The following code can read the first line of the OFF file and verify it:

use std::io::{BufRead, BufReader};

let buf = BufReader::new(file);
let mut lines_iter = buf.lines().map(|l| l.unwrap());

// Verify whether the first line of the OFF file is "OFF"
// Assert can be used for all types that implement Eq Trait_ eq!  Compare for equality
assert_eq!(lines_iter.next(), Some(String::from("OFF")));

The Lines method of BufReader returns an iterator of type Lines struct. The Lines structure provides an implementation of iterator trail. Iterators can be used to traverse data sequences. Iterator's map method can take a function as an argument and apply it to the data accessed by the iterator. In the above code, the map method accepts the closure | l| l.unwrap(). The iterator's next method moves the iterator backward to point to the next element as a return value. When the iterator executes next for the first time, the value obtained is the first value of the data sequence accessed by the iterator. If you continue to execute the next method, you can access the subsequent values of the data sequence in turn. This process is usually hidden in for Behind in grammar. If you use for In syntax, traverse each line of the file content, just

for line in buf.lines() {
    println!("{}", line.unwrap());
}

Because the second line of the OFF file defines the length of the point table and face table of the polyhedron structure, it determines that the process of traversing the content of the OFF file needs to be controlled by the counter. Therefore, in order to obtain the point table and face table conveniently, it is better to explicitly call the next method of the iterator than for In syntax is more appropriate.

Next, parse the second line of the OFF file to obtain the length information of the point table and face table:

let second_line = lines_iter.next().unwrap();
let mut split = second_line.split_whitespace();
let n_of_points: usize = split.next().unwrap().parse().unwrap();
let n_of_facets: usize = split.next().unwrap().parse().unwrap();

Due to line_ The return value of the ITER iterator is a reference containing an instance of type String, which is obtained after unwrap. Split of String_ The whitespace method splits instances of String type based on white space characters. In the last chapter, I used a simple state machine to implement split_str is for & STR type, and the segmentation results are saved in VEC < (use, use) >. Split of String_ The whitespace method does not return the split result of the String, but an iterator. If you use the split I implemented_ STR function to achieve the same functions as the above code, just

let second_line = lines_iter.next().unwrap();
let second_line_as_str = second_line.as_str();
let split = split_str(second_line_as_str);
let n_of_points: usize = second_line_as_str[(split[0].0 .. split[0].1)].parse().unwrap();
let n_of_facets: usize = second_line_as_str[(split[1].0 .. split[1].1)].parse().unwrap();

By contrast, the split of String is still used_ Whitespace method.

With the length of the point table and face table, you can use the iterator to continue reading the rest of the OFF file and analyze the point set information and face set information:

let mut mesh = Mesh::new();
for _i in 0 .. n_of_points {
    let line = lines_iter.next().unwrap();
    let mut p: Vec<f64> = Vec::new();
    for x in line.split_whitespace() {
        p.push(x.parse().unwrap());
    }
    mesh.points.push(p);
}
for _i in 0 .. n_of_facets {
    let line = lines_iter.next().unwrap();
    let mut f: Vec<usize> = Vec::new();
    let mut split = line.split_whitespace();
    let n:usize = split.next().unwrap().parse().unwrap(); // Ignore first element
    for x in split {
        f.push(x.parse().unwrap());
    }
    assert_eq!(n, f.len());
    mesh.facets.push(f);        
}

Note that in the above for In the in statement, the variable i is not used in the iteration process, which is recommended by rustc_ i replace. In addition, in the process of constructing the surface table, since the first number of the file content is the number of vertices of the patch, this information is only used to detect whether the number of vertices of the patch resolution result is correct.

How to verify the correctness of Mesh instances constructed based on OFF files? Since the Mesh type has implemented display trail, you might as well output its contents,

print!("{}", mesh);

Then compare the output content with the OFF file content.

Summary

use std::ops::Index;
use std::fmt::{Result, Formatter, Display};
use std::path::Path;
use std::fs::File;
use std::io::{BufRead, BufReader};

struct Mesh {
    points: Vec<Vec<f64>>,  // Point table
    facets: Vec<Vec<usize>> // Surface table
}

trait HasLength {
    fn len(&self) -> usize;
}

impl<T> HasLength for Vec<T> {
    fn len(&self) -> usize {
        return self.len();
    }
}

struct Prefix<T> {
    status: bool,
    body: fn(&T) -> String
}

impl<T> Prefix<T> {
    fn new() -> Prefix<T> {
        Prefix{status: false, body: |_| "".to_string()}
    }
}

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
                                               prefix: &Prefix<T>) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        if prefix.status {
            s += (prefix.body)(x).as_str();
        }
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points, &Prefix::new()).as_str();
        s += display_matrix(&self.facets, &Prefix{status: true,
                                                  body: |x| format!("{} ", x.len())}).as_str();
        write!(f, "{}", s)
    }
}

impl Mesh {
    fn new() -> Mesh {
        return Mesh {points: Vec::new(), facets: Vec::new()};
    }
    fn parse(&mut self, path: &str) {
        let path = Path::new(path);
        let file = File::open(path).unwrap();
        let buf = BufReader::new(file);
        
        let mut lines_iter = buf.lines().map(|l| l.unwrap());
        assert_eq!(lines_iter.next(), Some(String::from("OFF")));
        let second_line = lines_iter.next().unwrap();
        let mut split = second_line.split_whitespace();
        let n_of_points: usize = split.next().unwrap().parse().unwrap();
        let n_of_facets: usize = split.next().unwrap().parse().unwrap();

        for _i in 0 .. n_of_points {
            let line = lines_iter.next().unwrap();
            let mut p: Vec<f64> = Vec::new();
            for x in line.split_whitespace() {
                p.push(x.parse().unwrap());
            }
            self.points.push(p);
        }
        for _i in 0 .. n_of_facets {
            let line = lines_iter.next().unwrap();
            let mut f: Vec<usize> = Vec::new();
            let mut split = line.split_whitespace();
            let n:usize = split.next().unwrap().parse().unwrap(); // Ignore first element
            for x in split {
                f.push(x.parse().unwrap());
            }
            assert_eq!(n, f.len());
            self.facets.push(f);        
        }
    }
}

fn main() {
    let mut mesh = Mesh::new();
    mesh.parse("foo.off");
    print!("{}", mesh);
}

reference resources

[1] https://doc.rust-lang.org/std...
[2] https://doc.rust-lang.org/src...
[3] https://doc.rust-lang.org/sta...
[4] https://rustwiki.org/zh-CN/ru...
[5] https://gitee.com/garfileo/rh...

Tags: Rust

Posted by tharagleb on Thu, 14 Apr 2022 18:35:26 +0930