My polygon mesh generating/processing/morphing library using core.matrix is shaping up nicely: https://github.com/pkobrien/cad/tree/master/src/cad/mesh
Wow.... looking awesome!
How is performance? Have you identified any hotspots?
You are probably the first person to really test the 3D vector stuff in anger
@mikera: Thanks. I call that shape "spore" and it is one of my favorites.
Performance is about the same as the vector from http://thi.ng
I'm still working out some minor issues to get the code back to being solid.
Right now I'm not scaling my seed platonic solids properly. Basic stuff. I'm reading up on 3D graphics algorithms so I actually know what I'm doing at a lower level.
And I ran into an old issue with -0.0 in my normals but haven't been able to recreate it yet this morning.
That's why the old code applied an abs-zero
function when calculating the face normals.
Anyhow, I should have these issues resolved today. Then I will start looking at performance more.
Right now one of my main bottlenecks is in writing to the X3D xml files. I'm going to have to create a custom writer to make it lazier because I've maxed out all the tricks I can play with making things lazy using clojure.data.xml
Okay, scaling has been properly implemented and the platonic solid construction looks pretty good now.
@mikera: I need to get the absolute value of a -0.0 component of a Vector3
This is how I'm calculating a face normal now:
(defn normal
"Returns the ortho normal of the first three points passed in."
([[a b c]]
(normal a b c))
([a b c]
(mx/normalise! (mx/cross (mx/sub b a) (mx/sub c a)))))
The problem is that this can create a Vector3 having an x, y, or z component with a value of -0.0 and Clojure has a bug related to that which messes up some other functions of mine.
That's the Clojure bug: 0.0 and -0.0 compare equal but have different hash values
So my fix in my old code was to do this:
(defn normal
"Returns the ortho normal of the first three points passed in."
([[a b c]] (normal a b c))
;([a b] (gc/normalize (gc/cross a b)))
([a b c] (apply point (mapv (comp mu/round2safe mu/abs-zero)
(mx/normalise (mx/cross (- b a) (- c a)))))))
(defn abs-zero
[x]
(if (zero? x) 0.0 x))
@mikera: Now that I'm using core.matrix what's the best way to apply abs-zero
to a Vector3
as part of my normal
function?
@meow: I'd probably do something like this:
(def ZERO-VECTOR3 (Vector3/of 0.0 0.0 0.0)) (defn abs-zero [^Vector3 v] (if (.isZero v) ZERO-VECTOR3 v))
i.e. replace with a constant zero Vector3 whenever required. Avoids unnecessary new allocations.
if you want to be a bit safer and support arbitrary vector types, you can use:
(defn abs-zero [v] (if (zero-matrix? v) ZERO-VECTOR3 v))
That costs you one extra protocol dispatch.... so not too much but might be painful in an inner loop
@mikera: We're not quite on the same page. I only need to replace one component of the Vector3 if it is -0.0. The other components might be okay. So, for example [1.0 -0.0 5.25]
except with normalized values...
so after calling normalise!
I need to replace -0.0 components.
not the entire vector3
Hmmmm this is a problem because you are hashing them?
yes
and the clojure bug screws that up
Hmmm
since this is a calculated vector I can mutate it in place
OK try this
(defn abs-zero ^double [^double v] (if (== 0.0 v) 0.0 v))
(emap abs-zero (Vector3/of -0.0 0.0 -1.0))
=> #vectorz/vector [0.0,0.0,-1.0]
That work? emap is like mapv but for core.matrix arrays
yes, emap is what I need
vectorz-clj can also optimise these if there are primitive type hints
It is about 5x faster than a Clojure mapv on my machine, even with small vectors like this
sweet
you prefer (== 0.0 v)
over (zero? v)
?
I think they work the same
zero? might be better
About to do a new release of vectorz-clj.... any issues discovered that you'd like a fix for?
No, all the problems I'm having are my own doing. Haven't found any problems with core.matrix so far. :simple_smile:
that fix works like a charm: (mx/emap mu/abs-zero (mx/normalise! (mx/cross (mx/sub b a) (mx/sub c a))))
oh, I can make that (mx/emap! mu/abs-zero (mx/normalise! (mx/cross (mx/sub b a) (mx/sub c a))))
can't I?
emap!
instead of emap
?
@mikera: I'm getting this error: CompilerException java.lang.ClassCastException: mikera.vectorz.Vector3 cannot be cast to java.lang.Number,
when I try to do this:
opposite-face (fn [outer-face thickness]
(vec (for [vert (reverse outer-face)]
(+ vert
(* (vert-normal-map vert) (- thickness))))))
actually, the problem lies elsewhere, sorry
ah, this is failing:
(defn normal
"Returns the normal for the vertex as the average of the face normals."
[mesh vert]
(let [face-normal-map (mp/face-normal-map mesh)
vert-faces-map (mp/vert-faces-map mesh)
xf (comp (map face-normal-map) (distinct))
vnorm (fn [vert]
(->> (vert-faces-map vert)
(transduce xf + (mc/point 0 0 0))
(mx/normalise!)))]
(vnorm vert)))
Are your + and * the core.matrix operators or the clojure ones? That's the error I'd expect if you try and pass a vector to a clojure numerical operator
Also worth checking the types of values are correct at each stage of your operations. I recommend unit tests to do that, especially for the results of functions like face-normal-map
ah, I hadn't imported the operators into this module - mx/add
fixed it
I have no unit tests yet - this has been a WIP - I've only been doing the mesh processing for a couple of weeks and only a week ago decided to create my own library, so I've only just fleshed this out. Once I'm happy with the overall shape, and I think it has shaped up nicely, I will move all this mesh stuff over to my decomplect.ion
namespace/github repository and will add unit tests.