Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Automatically calculate normals as needed for meshes missing normal data

Also, validate mesh when #data is accessed.
  • Loading branch information...
commit 1716a6197acd701bbefbadc970f07ee1181f3a58 1 parent 1739b49
@sinisterchipmunk authored
View
36 lib/assets/javascripts/jax/mesh.js.coffee
@@ -10,7 +10,7 @@ class Mesh
constructor: (options) ->
@_valid = false
- @_data = new Jax.Mesh.Data
+ @data = new Jax.Mesh.Data
@_bounds = new Jax.Mesh.Bounds
@_color = new Jax.Color
@_initialized = false
@@ -48,8 +48,10 @@ class Mesh
@_data
set: (d) ->
@invalidate()
+ @_data.dispose() if @_data
@_data = d
@_data.addEventListener 'colorChanged', => @fireEvent 'colorChanged'
+ @_data.addEventListener 'shouldRecalculateNormals', => @recalculateNormals()
@define 'color'
get: -> @_color
@@ -82,7 +84,35 @@ class Mesh
@_submesh = submesh
draw_mode: GL_POINTS
-
+
+ ###
+ Immediately recalculates this mesh's vertex normals.
+
+ This method is meant to be overridden by subclasses. The default implementation just
+ builds a vector from the calculated center of the mesh to each vertex and normalizes
+ that vector.
+
+ Note that if this mesh has more than 65535 vertices, its sub-mesh will not automatically
+ have its normals recalculated, so you'll need to call `mesh.submesh.recalculateNormals()`.
+
+ Returns true.
+ ###
+ recalcNormal = vec3.create()
+ recalculateNormals: ->
+ normals = @data.normalBuffer
+ vertices = @data.vertexBuffer
+ center = @bounds.center
+ for i in [0...vertices.length] by 3
+ recalcNormal[0] = vertices[i]
+ recalcNormal[1] = vertices[i+1]
+ recalcNormal[2] = vertices[i+2]
+ vec3.subtract recalcNormal, center, recalcNormal
+ vec3.normalize recalcNormal
+ normals[i ] = recalcNormal[0]
+ normals[i+1] = recalcNormal[1]
+ normals[i+2] = recalcNormal[2]
+ true
+
render: (context, model, material) ->
@validate() unless @_valid
if material
@@ -128,7 +158,7 @@ class Mesh
[vertices, colors, textures, normals, indices] = [[], [], [], [], []]
@init vertices, colors, textures, normals, indices
@submesh = @split vertices, colors, textures, normals, indices if vertices.length > 65535*3
- @_data = new Jax.Mesh.Data vertices, colors, textures, normals, indices
+ @data = new Jax.Mesh.Data vertices, colors, textures, normals, indices
@_data.color = @_color
this
View
57 lib/assets/javascripts/jax/mesh/data.js.coffee
@@ -134,12 +134,45 @@ class Jax.Mesh.Data
@bind @_context unless @_bound
for key, target of mapping
- switch key
- when 'vertices' then vars.set target, @vertexWrapper
- when 'colors' then vars.set target, @colorWrapper
- when 'textures' then vars.set target, @textureCoordsWrapper
- when 'normals' then vars.set target, @normalWrapper
- else throw new Error "Mapping key must be one of 'vertices', 'colors', 'textures', 'normals'"
+ if vars.set
+ # TODO phase out vars.set in favor of direct assignment
+ switch key
+ when 'vertices' then vars.set target, @vertexWrapper
+ when 'colors' then vars.set target, @colorWrapper
+ when 'textures' then vars.set target, @textureCoordsWrapper
+ when 'normals'
+ @recalculateNormals() if @shouldRecalculateNormals()
+ vars.set target, @normalWrapper
+ else throw new Error "Mapping key must be one of 'vertices', 'colors', 'textures', 'normals'"
+ else
+ switch key
+ when 'vertices' then vars[target] = @vertexWrapper
+ when 'colors' then vars[target] = @colorWrapper
+ when 'textures' then vars[target] = @textureCoordsWrapper
+ when 'normals'
+ @recalculateNormals() if @shouldRecalculateNormals()
+ vars[target] = @normalWrapper
+ else throw new Error "Mapping key must be one of 'vertices', 'colors', 'textures', 'normals'"
+
+ ###
+ Requests this data set's normals to be recalculated. Note that this does not directly
+ perform the recalculation. Instead, it fires a `shouldRecalculateNormals` event, so
+ that the object containing this mesh data can control the method in which normals
+ are calculated. For example, a point cloud might calculate its normals entirely
+ differently from a triangle mesh, and it is not the responsibility of `Jax.Mesh.Data`
+ to keep track of which algorithm it should use.
+ ###
+ recalculateNormals: () ->
+ @fireEvent 'shouldRecalculateNormals'
+ @_shouldRecalculateNormals = false
+ @invalidate()
+ true
+
+ ###
+ Returns true if the mesh data has detected that its normal data should be recalculated.
+ ###
+ shouldRecalculateNormals: () ->
+ return @_shouldRecalculateNormals
###
Allocate or reallocate the typed array buffer and data views. This is called during
@@ -190,21 +223,15 @@ class Jax.Mesh.Data
_csize = 4 * Float32Array.BYTES_PER_ELEMENT
_array_buffer = @_array_buffer
length = @length
+ if normals.length is 0 then @_shouldRecalculateNormals = true
+ else @_shouldRecalculateNormals = false
for ofs in [0...length]
[vofs, cofs, tofs] = [ofs * 3, ofs * 4, ofs * 2]
_vbuf[vofs ] = vertices[vofs ]
_vbuf[vofs+1] = vertices[vofs+1]
_vbuf[vofs+2] = vertices[vofs+2]
- if normals.length <= vofs
- tmpvec3[0] = vertices[vofs]
- tmpvec3[1] = vertices[vofs+1]
- tmpvec3[2] = vertices[vofs+2]
- vec3.normalize tmpvec3
- _nbuf[vofs ] = tmpvec3[0]
- _nbuf[vofs+1] = tmpvec3[1]
- _nbuf[vofs+2] = tmpvec3[2]
- else
+ unless @_shouldRecalculateNormals
_nbuf[vofs ] = normals[vofs ]
_nbuf[vofs+1] = normals[vofs+1]
_nbuf[vofs+2] = normals[vofs+2]
View
178 lib/assets/javascripts/jax/mesh/support.js
@@ -1,104 +1,106 @@
// Support functions used by Jax.Mesh
-Jax.OldMesh.prototype.eachTriangle = function(callback) {
- var mesh = this;
- var vertcount, a;
+if (Jax.OldMesh) {
+ Jax.OldMesh.prototype.eachTriangle = function(callback) {
+ var mesh = this;
+ var vertcount, a;
- var indices = mesh.getIndexBuffer(), vertices = mesh.vertices;
- if (vertices.length == 0) return;
+ var indices = mesh.getIndexBuffer(), vertices = mesh.vertices;
+ if (vertices.length == 0) return;
- if (indices) {
- if (indices.length == 0) return;
- indices = indices.getTypedArray();
- vertcount = indices.length;
- } else
- vertcount = vertices.length;
+ if (indices) {
+ if (indices.length == 0) return;
+ indices = indices.getTypedArray();
+ vertcount = indices.length;
+ } else
+ vertcount = vertices.length;
- function call(i1, i2, i3) {
- var v1, v2, v3, i = false;
+ function call(i1, i2, i3) {
+ var v1, v2, v3, i = false;
- if (indices) {
- i = true;
- v1 = vertices[indices[i1]];
- v2 = vertices[indices[i2]];
- v3 = vertices[indices[i3]];
- } else {
- i = false;
- v1 = vertices[i1];
- v2 = vertices[i2];
- v3 = vertices[i3];
- }
+ if (indices) {
+ i = true;
+ v1 = vertices[indices[i1]];
+ v2 = vertices[indices[i2]];
+ v3 = vertices[indices[i3]];
+ } else {
+ i = false;
+ v1 = vertices[i1];
+ v2 = vertices[i2];
+ v3 = vertices[i3];
+ }
- if (!v1 || !v2 || !v3) return;
- callback(v1.array, v2.array, v3.array);
- }
+ if (!v1 || !v2 || !v3) return;
+ callback(v1.array, v2.array, v3.array);
+ }
- switch(mesh.draw_mode) {
- case GL_TRIANGLE_STRIP:
- for (a = 2; a < vertcount; a += 2) {
- call(a-2, a-1, a);
- call(a, a-1, a+1);
- }
- break;
- case GL_TRIANGLES:
- for (a = 0; a < vertcount; a += 3)
- call(a, a+1, a+2);
- break;
- case GL_TRIANGLE_FAN:
- for (a = 2; a < vertcount; a++)
- call(0, a-1, a);
- break;
- default:
- return;
- }
-};
+ switch(mesh.draw_mode) {
+ case GL_TRIANGLE_STRIP:
+ for (a = 2; a < vertcount; a += 2) {
+ call(a-2, a-1, a);
+ call(a, a-1, a+1);
+ }
+ break;
+ case GL_TRIANGLES:
+ for (a = 0; a < vertcount; a += 3)
+ call(a, a+1, a+2);
+ break;
+ case GL_TRIANGLE_FAN:
+ for (a = 2; a < vertcount; a++)
+ call(0, a-1, a);
+ break;
+ default:
+ return;
+ }
+ };
-Jax.OldMesh.prototype.buildTriangles = function() {
- var mesh = this;
- mesh.triangles.clear();
+ Jax.OldMesh.prototype.buildTriangles = function() {
+ var mesh = this;
+ mesh.triangles.clear();
- mesh.eachTriangle(function(v1, v2, v3) {
- var tri = new Jax.Geometry.Triangle();
- tri.assign(v1, v2, v3);
- mesh.triangles.push(tri);
- });
-};
+ mesh.eachTriangle(function(v1, v2, v3) {
+ var tri = new Jax.Geometry.Triangle();
+ tri.assign(v1, v2, v3);
+ mesh.triangles.push(tri);
+ });
+ };
-Jax.OldMesh.prototype.calculateBounds = function(vertices) {
- var self = this;
- if (vertices.length == 0) {
- self.bounds.left = self.bounds.right = 0;
- self.bounds.top = self.bounds.bottom = 0;
- self.bounds.front = self.bounds.back = 0;
- self.bounds.width = self.bounds.height = self.bounds.depth = 0;
- } else {
- self.bounds.left = self.bounds.right = null;
- self.bounds.top = self.bounds.bottom = null;
- self.bounds.front = self.bounds.back = null;
- self.bounds.width = self.bounds.height = self.bounds.depth = null;
- }
+ Jax.OldMesh.prototype.calculateBounds = function(vertices) {
+ var self = this;
+ if (vertices.length == 0) {
+ self.bounds.left = self.bounds.right = 0;
+ self.bounds.top = self.bounds.bottom = 0;
+ self.bounds.front = self.bounds.back = 0;
+ self.bounds.width = self.bounds.height = self.bounds.depth = 0;
+ } else {
+ self.bounds.left = self.bounds.right = null;
+ self.bounds.top = self.bounds.bottom = null;
+ self.bounds.front = self.bounds.back = null;
+ self.bounds.width = self.bounds.height = self.bounds.depth = null;
+ }
- var i, v;
+ var i, v;
- for (i = 0; i < vertices.length; i++)
- {
- // x, i % 3 == 0
- v = vertices[i];
- if (self.bounds.left == null || v < self.bounds.left) self.bounds.left = v;
- if (self.bounds.right == null || v > self.bounds.right) self.bounds.right = v;
+ for (i = 0; i < vertices.length; i++)
+ {
+ // x, i % 3 == 0
+ v = vertices[i];
+ if (self.bounds.left == null || v < self.bounds.left) self.bounds.left = v;
+ if (self.bounds.right == null || v > self.bounds.right) self.bounds.right = v;
- // y, i % 3 == 1
- v = vertices[++i];
- if (self.bounds.bottom== null || v < self.bounds.bottom) self.bounds.bottom = v;
- if (self.bounds.top == null || v > self.bounds.top) self.bounds.top = v;
+ // y, i % 3 == 1
+ v = vertices[++i];
+ if (self.bounds.bottom== null || v < self.bounds.bottom) self.bounds.bottom = v;
+ if (self.bounds.top == null || v > self.bounds.top) self.bounds.top = v;
- // z, i % 3 == 2
- v = vertices[++i];
- if (self.bounds.front == null || v > self.bounds.front) self.bounds.front = v;
- if (self.bounds.back == null || v < self.bounds.back) self.bounds.back = v;
- }
+ // z, i % 3 == 2
+ v = vertices[++i];
+ if (self.bounds.front == null || v > self.bounds.front) self.bounds.front = v;
+ if (self.bounds.back == null || v < self.bounds.back) self.bounds.back = v;
+ }
- self.bounds.width = self.bounds.right - self.bounds.left;
- self.bounds.height= self.bounds.top - self.bounds.bottom;
- self.bounds.depth = self.bounds.front - self.bounds.back;
-};
+ self.bounds.width = self.bounds.right - self.bounds.left;
+ self.bounds.height= self.bounds.top - self.bounds.bottom;
+ self.bounds.depth = self.bounds.front - self.bounds.back;
+ };
+}
View
61 lib/assets/javascripts/jax/mesh/triangles.js.coffee
@@ -6,6 +6,62 @@ class Jax.Mesh.Triangles extends Jax.Mesh.Base
hash = (vx, vy, vz, cr, cg, cb, ca, ts, tt, nx, ny, nz) ->
"#{vx},#{vy},#{vz},#{cr},#{cg},#{cb},#{ca},#{ts},#{tt},#{nx},#{ny},#{nz}"
+ recalcTri = new Jax.Geometry.Triangle()
+ recalcNormal = vec3.create()
+ recalculateNormals: ->
+ # Calculates vertex normals in 2 passes. First pass, accumulate normals.
+ # Second pass, average accumulated normals.
+ tri = recalcTri
+ recalcNormals = {}
+ data = @data
+ [vertices, colors, textures, normals] = \
+ [data.vertexBuffer, data.colorBuffer, data.textureCoordsBuffer, data.normalBuffer]
+
+ hashAt = (i, vx, vy, vz) ->
+ [cr, cg, cb, ca, ts, tt, nx, ny, nz] = [ \
+ colors[i*4], colors[i*4+1], colors[i*4+2], colors[i*4+3], \
+ textures[i*2], textures[i*2+1], \
+ normals[i*3], normals[i*3+1], normals[i*3+2] ]
+ hash vx, vy, vz, cr, cg, cb, ca, ts, tt, nx, ny, nz
+
+ # First pass: accumulation - for each triangle, add the face normal
+ # to an array for vertex a, b and c
+ for i in [0...data.length] by 3
+ [ai, bi, ci] = [i, i+1, i+2]
+ [avx, avy, avz] = [ vertices[ai*3], vertices[ai*3+1], vertices[ai*3+2] ]
+ [bvx, bvy, bvz] = [ vertices[bi*3], vertices[bi*3+1], vertices[bi*3+2] ]
+ [cvx, cvy, cvz] = [ vertices[ci*3], vertices[ci*3+1], vertices[ci*3+2] ]
+ tri.setComponents avx, avy, avz, bvx, bvy, bvz, cvx, cvy, cvz
+ normal = tri.getNormal()
+ # a
+ vhash = hashAt ai, avx, avy, avz
+ rn = recalcNormals[vhash] or= []
+ rn.push normal[0], normal[1], normal[2]
+ # b
+ vhash = hashAt bi, bvx, bvy, bvz
+ rn = recalcNormals[vhash] or= []
+ rn.push normal[0], normal[1], normal[2]
+ # c
+ vhash = hashAt ci, cvx, cvy, cvz
+ rn = recalcNormals[vhash] or= []
+ rn.push normal[0], normal[1], normal[2]
+
+ # Second pass: average - for each vertex, average the accumulated normals together
+ for i in [0...data.length]
+ [vx, vy, vz] = [ vertices[i*3], vertices[i*3+1], vertices[i*3+2] ]
+ recalcNormal[0] = recalcNormal[1] = recalcNormal[2] = 0
+ vhash = hashAt i, vx, vy, vz
+ rn = recalcNormals[vhash]
+ rnlen = rn.length
+ for ni in [0...rnlen] by 3
+ recalcNormal[0] += rn[ni ]
+ recalcNormal[1] += rn[ni+1]
+ recalcNormal[2] += rn[ni+2]
+ vec3.scale recalcNormal, 3 / rnlen # 1 / (rn.length / 3)
+ data.normalBuffer[i*3 ] = recalcNormal[0]
+ data.normalBuffer[i*3+1] = recalcNormal[1]
+ data.normalBuffer[i*3+2] = recalcNormal[2]
+
split: (vertices, colors, textures, normals, indices) ->
max = 65535
return null if vertices.length <= max * 3
@@ -15,6 +71,11 @@ class Jax.Mesh.Triangles extends Jax.Mesh.Base
_v = []
_i = []
tracker = {}
+
+ # It's easier to unravel indices and put flat vertices into the new vertices array,
+ # but it's more efficient to track those vertices and create corresponding unique
+ # indices on the fly. Since we have to iterate through indices either way, performance
+ # difference is negligible.
for i in [0...indices.length] by 3
i1 = indices[i]
View
6 spec/javascripts/helpers/jax_spec_helper.js
@@ -41,7 +41,11 @@ beforeEach(function() {
},
toBeTrue: function() {
- return this.actual == true;
+ return this.actual === true;
+ },
+
+ toBeFalse: function() {
+ return this.actual === false;
},
toBeUndefined: function() {
View
27 spec/javascripts/jax/mesh/data_spec.js.coffee
@@ -59,8 +59,31 @@ describe "Jax.Mesh.Data", ->
it "should default color to white", ->
expect(data.colorBuffer).toEqualVector [1, 1, 1, 1]
- it "should default normal to normalized vertex position", ->
- expect(data.normalBuffer).toEqualVector vec3.normalize([1, 2, 3])
+ describe "when normals are bound", ->
+ beforeEach -> data.context = @context
+
+ it "should fire a shouldRecalculateNormals event", ->
+ fired = false
+ data.addEventListener 'shouldRecalculateNormals', -> fired = true
+ data.set {}, normals: 'NORMS'
+ expect(fired).toBeTrue()
+
+ it "should not fire a shouldRecalculateNormals event for subsequent bindings", ->
+ count = 0
+ data.addEventListener 'shouldRecalculateNormals', -> count++
+ data.set {}, normals: 'NORMS'
+ data.set {}, normals: 'NORMS'
+ data.set {}, normals: 'NORMS'
+ expect(count).toEqual 1
+
+ describe "when normals are not bound", ->
+ beforeEach -> data.context = @context
+
+ it "should not fire a shouldRecalculateNormals event", ->
+ fired = false
+ data.addEventListener 'shouldRecalculateNormals', -> fired = true
+ data.set {}, vertices: 'VERTS'
+ expect(fired).toBeFalse()
it "should default textures to 0", ->
expect(data.textureCoordsBuffer).toEqualVector [0, 0]
View
35 spec/javascripts/jax/mesh/triangles_spec.js.coffee
@@ -0,0 +1,35 @@
+describe "Jax.Mesh.Triangles", ->
+ mesh = material = null
+
+ describe "with more than 65535 vertices", ->
+ beforeEach -> mesh = new Jax.Mesh.Triangles(init: (v) -> v.push i for i in [0...(65535*3+9)])
+
+ it "should produce a Triangles sub-mesh", ->
+ expect(mesh.submesh).toBeInstanceOf Jax.Mesh.Triangles
+
+ it "should not have more than 65535 vertices", ->
+ expect(mesh.data.vertexBuffer.length).not.toBeGreaterThan 65535*3
+
+ it "should not reference higher vertex indices", ->
+ for i in mesh.data.indexBuffer
+ expect(i).not.toBeGreaterThan 65535
+
+ it "should have a sub-mesh with vertices", ->
+ expect(mesh.submesh.data.vertexBuffer.length).not.toEqual 0
+
+ beforeEach ->
+ mesh = new Jax.Mesh.Triangles init: (v) -> v.push 0, 1, 0, -1, 0, 0, 1, 0, 0
+
+ it "should calculate correct normals", ->
+ mesh.recalculateNormals()
+ expect(mesh.data.normalBuffer).toEqualVector [0, 0, 1, 0, 0, 1, 0, 0, 1]
+
+ it "should be rendered as GL_TRIANGLES", ->
+ mat = new Jax.Material
+ mat.render = (context, _mesh, options) ->
+ # after all that setup, here's the real test...
+ expect(_mesh.draw_mode).toEqual GL_TRIANGLES
+ spyOn(mat, 'render').andCallThrough()
+ mesh.render "context", "model", mat
+ expect(mat.render).toHaveBeenCalled()
+
View
58 spec/javascripts/jax/mesh_spec.js.coffee
@@ -1,22 +1,31 @@
describe "Jax.Mesh", ->
- mesh = null
+ mesh = material = null
- describe "Triangles with more than 65535 vertices", ->
- beforeEach -> mesh = new Jax.Mesh.Triangles(init: (v) -> v.push i for i in [0...(65535*3+9)])
-
- it "should produce a Triangles sub-mesh", ->
- expect(mesh.submesh).toBeInstanceOf Jax.Mesh.Triangles
-
- it "should not have more than 65535 vertices", ->
- expect(mesh.data.vertexBuffer.length).not.toBeGreaterThan 65535*3
+ describe "initialized without normal data", ->
+ beforeEach ->
+ mesh = new Jax.Mesh.Base(init: (v) -> v.push 1, 1, 1, 2, 2, 2)
+ @world.addLight new Jax.Light.Directional # to ensure normals get assigned
+
+ it "should recalculate its normals only once when rendered twice", ->
+ spyOn mesh, 'recalculateNormals'
+ mesh.render @context, new Jax.Model
+ mesh.render @context, new Jax.Model
+ expect(mesh.recalculateNormals.callCount).toBe 1
- it "should not reference higher vertex indices", ->
- for i in mesh.data.indexBuffer
- expect(i).not.toBeGreaterThan 65535
-
- it "should have a sub-mesh with vertices", ->
- expect(mesh.submesh.data.vertexBuffer.length).not.toEqual 0
+ it "should set each normal to the vertex direction relative to calculated mesh center", ->
+ # calculated center should be 1.5, 1.5, 1.5, making the normals [-1,-1,-1], [1,1,1]
+ mesh.render @context, new Jax.Model
+ expect(mesh.data.normalBuffer).toEqualVector [vec3.normalize([-1,-1,-1])..., vec3.normalize([1,1,1])...]
+
+ describe "with a submesh", ->
+ beforeEach -> mesh.submesh = new Jax.Mesh.Base(init: (v) -> v.push 2, 3, 4)
+ it "should recalculate normals of its submesh exactly once", ->
+ spyOn mesh.submesh, 'recalculateNormals'
+ mesh.render @context, new Jax.Model
+ mesh.render @context, new Jax.Model
+ expect(mesh.submesh.recalculateNormals.callCount).toBe 1
+
describe "TriangleStrip with more than 65535 vertices", ->
beforeEach -> mesh = new Jax.Mesh.TriangleStrip(init: (v) -> v.push i for i in [0...(65535*3+9)])
@@ -53,7 +62,7 @@ describe "Jax.Mesh", ->
it "should make its data available immediately after creation", ->
- mesh = new Jax.Mesh.Triangles(init: (v) -> v.push 1, 1, 1)
+ mesh = new Jax.Mesh.Base(init: (v) -> v.push 1, 1, 1)
expect(mesh.data.vertexBuffer).toEqualVector [1, 1, 1]
it "should be valid after rebuilding", ->
@@ -62,7 +71,7 @@ describe "Jax.Mesh", ->
expect(mesh).toBeValid()
it "should always invoke #init during rebuild, even if already valid", ->
- mesh = new Jax.Mesh.Triangles(init: ->)
+ mesh = new Jax.Mesh.Base(init: ->)
mesh.validate()
spyOn mesh, 'init'
mesh.rebuild()
@@ -70,7 +79,7 @@ describe "Jax.Mesh", ->
it "should set 'this' in #render to the mesh instance", ->
self = null
- class M extends Jax.Mesh.Triangles
+ class M extends Jax.Mesh.Base
init: (v) -> v.push(0, 0, 0)
render: (v) -> self = this
mesh = new M()
@@ -88,19 +97,6 @@ describe "Jax.Mesh", ->
mesh = new Jax.Mesh.Base size: 5
expect(mesh.size).toBe 5
- describe "Triangles", ->
- beforeEach ->
- mesh = new Jax.Mesh.Triangles init: (v) -> v.push 0, 1, 0, -1, 0, 0, 1, 0, 0
-
- it "should be rendered as GL_TRIANGLES", ->
- mat = new Jax.Material
- mat.render = (context, _mesh, options) ->
- # after all that setup, here's the real test...
- expect(_mesh.draw_mode).toEqual GL_TRIANGLES
- spyOn(mat, 'render').andCallThrough()
- mesh.render "context", "model", mat
- expect(mat.render).toHaveBeenCalled()
-
describe "Base", ->
beforeEach -> mesh = new Jax.Mesh.Base()
Please sign in to comment.
Something went wrong with that request. Please try again.