2011-01-26 112 views
3

我想解析Wavefront OBJ file中的文本行。目前我只對「V」和「F」類型感興趣。 我的算法如下:如何讓這段代碼更簡單,更清晰,更「寧靜」?

  1. 檢查,如果行不爲零(否則第2步會失敗)「#」
  2. 下降後評論和內飾空間
  3. 下降前綴「V」或「F」
  4. 將字符串拆分爲元素列表,其中每個元素
    1. 如果它是像符號一樣被分割爲列表| 34/76/23 |
    2. 已從列表轉換:我只接受一個元素,第一個默認爲
    3. 或強制爲給定類型(如果它已經是原子序號)。

下面是代碼:

(defun parse-line (line prefix &key (type 'single-float)) 
    (declare (optimize (debug 3))) 
    (labels ((rfs (what) 
      (read-from-string (concatenate 'string "(" what ")"))) 
      (unpack (str &key (char #\/) (n 0)) 
      (let ((*readtable* (copy-readtable))) 
       (when char ;; we make the given char a delimiter (space) 
       (set-syntax-from-char char #\Space)) 
       (typecase str 
       ;; string -> list of possibly symbols. 
       ;; all elements are preserved by (map). nil's are dropped 
       (string (delete-if #'null 
            (map 'list 
             #'unpack 
             (rfs str)))) 
       ;; symbol -> list of values 
       (symbol (unpack (rfs (symbol-name str)))) 
       ;; list -> value (only the requested one) 
       (list (unpack (nth n str))) 
       ;; value -> just coerce to type 
       (number (coerce str type)))))) 
    (and line 
     (setf line (string-trim '(#\Space #\Tab) 
           (subseq line 0 (position #\# line)))) 
     (< (length prefix) (length line)) 
     (string= line prefix :end1 (length prefix) :end2 (length prefix)) 
     (setf line (subseq line (length prefix))) 
     (let ((value (unpack line :char nil))) 
      (case (length value) 
       (3 value) 
       (4 (values (subseq value 0 3) ;; split quad 0-1-2-3 on tri 0-1-2 + tri 0-2-3 
          (list (nth 0 value) 
           (nth 2 value) 
           (nth 3 value))))))))) 

第四步(標籤 「解壓縮」)是一種遞歸的。這是一個功能,可以自己調用三次。

無論如何,這個解決方案似乎很笨拙。

我的問題是:如何用更短,更清晰的代碼解決這個任務?

+1

如何使代碼更lispy?當然,還有更多的括號。此外,全球搜索和替換的''''' – 2011-01-26 07:04:02

回答

5

我會以更結構化的方式解決這個問題。

要分析OBJ文件成某種數據結構:

(defun parse-obj-file (filespec) 
    ;; todo 
) 

你需要考慮數據結構如何返回應該看看。現在,讓我們返回兩個列表的列表,其中一個頂點是其中一個面。解析器將通過每線,確定它是否是一個頂點或面,然後收集到適當的列表:

(defun parse-obj-file (filespec) 
    (with-open-file (in-stream filespec 
          :direction :input) 
    (loop for line = (read-line in-stream nil) 
      while line 
      when (cl-ppcre:scan "^v " line) 
      collect (parse-vertex line) into vertices 
      when (cl-ppcre:scan "^f " line) 
      collect (parse-face line) into faces 
      finally (return (list vertices faces))))) 

我用這裏的cl-ppcre庫,但是你也可以使用mismatchsearch 。然後您需要定義parse-vertexparse-face,其中cl-ppcre:split應該非常方便。

定義頂點和麪的類可能也很有用。

更新:這是我如何將接近頂點:

(defclass vertex() 
    ((x :accessor x :initarg :x) 
    (y :accessor y :initarg :y) 
    (z :accessor z :initarg :z) 
    (w :accessor w :initarg :w))) 

(defun parse-vertex (line) 
    (destructuring-bind (label x y z &optional w) 
     (cl-ppcre:split "\\s+" (remove-comment line)) 
    (declare (ignorable label)) 
    (make-instance 'vertex 
        :x (parse-number x) 
        :y (parse-number y) 
        :z (parse-number z) 
        :w (parse-number w)))) 

Parse-numberparse-number庫。這比使用read好。

更新2:(對不起,使這是一個故事,我必須交錯一些工作。)一張臉由一系列的臉點組成。

(defclass face-point() 
    ((vertex-index :accessor vertex-index :initarg :vertex-index) 
    (texture-coordinate :accessor texture-coordinate 
         :initarg :texture-coordinate) 
    (normal :accessor normal :initarg :normal))) 

(defun parse-face (line) 
    (destructuring-bind (label &rest face-points) 
     (cl-ppcre:split "\\s+" (remove-comment line)) 
    (declare (ignorable label)) 
    (mapcar #'parse-face-point face-points))) 

(defun parse-face-point (string) 
    (destructuring-bind (vertex-index &optional texture-coordinate normal) 
     (cl-ppcre:split "/" string) 
    (make-instance 'face-point 
        :vertex-index vertex-index 
        :texture-coordinate texture-coordinate 
        :normal normal))) 

Remove-comment簡單地扔掉第一#後一切:

(defun remove-comment (line) 
    (subseq line 0 (position #\# line))) 
+0

謝謝你的答案!不幸的是,我不能在這裏評論我的遺留代碼。所以我只是提到,我開始使用(removecomment ...)函數,但後來決定,將#\#當作#\\)更容易。 – avp 2011-01-26 13:33:18