# frozen_string_literal: true module MoonModel class Mesh attr_reader :name, :vertices, :triangles, :material def initialize(name:, material: nil) @name = name @material = material @vertices = [] @triangles = [] end def vertex(point) @vertices << point.map(&:to_f) @vertices.length - 1 end def triangle(a, b, c) @triangles << [a, b, c] end def quad(a, b, c, d, flip: false) flip ? (triangle(a, c, b); triangle(a, d, c)) : (triangle(a, b, c); triangle(a, c, d)) end def add_box(center, size) cx, cy, cz = center sx, sy, sz = size.map { |v| v / 2.0 } ids = [[-sx,-sy,-sz],[sx,-sy,-sz],[sx,sy,-sz],[-sx,sy,-sz],[-sx,-sy,sz],[sx,-sy,sz],[sx,sy,sz],[-sx,sy,sz]].map do |x,y,z| vertex([cx+x, cy+y, cz+z]) end quad(ids[0], ids[3], ids[2], ids[1]); quad(ids[4], ids[5], ids[6], ids[7]) quad(ids[0], ids[1], ids[5], ids[4]); quad(ids[1], ids[2], ids[6], ids[5]) quad(ids[2], ids[3], ids[7], ids[6]); quad(ids[3], ids[0], ids[4], ids[7]) end def add_tapered_box(center, length, width_a, width_b, height) cx, cy, cz = center x0 = cx - length / 2.0; x1 = cx + length / 2.0 z0 = cz - height / 2.0; z1 = cz + height / 2.0 ids = [[x0,cy-width_a/2,z0],[x0,cy+width_a/2,z0],[x1,cy+width_b/2,z0],[x1,cy-width_b/2,z0], [x0,cy-width_a/2,z1],[x0,cy+width_a/2,z1],[x1,cy+width_b/2,z1],[x1,cy-width_b/2,z1]].map { |p| vertex(p) } quad(ids[0],ids[3],ids[2],ids[1]); quad(ids[4],ids[5],ids[6],ids[7]) quad(ids[0],ids[1],ids[5],ids[4]); quad(ids[1],ids[2],ids[6],ids[5]) quad(ids[2],ids[3],ids[7],ids[6]); quad(ids[3],ids[0],ids[4],ids[7]) end def add_cylinder(center, radius, height, segments: 24) cx, cy, cz = center bottom = segments.times.map { |i| a = i * 2*Math::PI/segments; vertex([cx+radius*Math.cos(a), cy+radius*Math.sin(a), cz-height/2]) } top = segments.times.map { |i| a = i * 2*Math::PI/segments; vertex([cx+radius*Math.cos(a), cy+radius*Math.sin(a), cz+height/2]) } cb = vertex([cx,cy,cz-height/2]); ct = vertex([cx,cy,cz+height/2]) segments.times do |i| j = (i+1)%segments triangle(cb,bottom[j],bottom[i]); triangle(ct,top[i],top[j]); quad(bottom[i],bottom[j],top[j],top[i]) end end def add_annular_cylinder(center, inner_radius, outer_radius, z0, z1, segments: 32) cx, cy = center oi=[]; oo=[]; ti=[]; to=[] segments.times do |i| a=i*2*Math::PI/segments oi << vertex([cx+inner_radius*Math.cos(a),cy+inner_radius*Math.sin(a),z0]) oo << vertex([cx+outer_radius*Math.cos(a),cy+outer_radius*Math.sin(a),z0]) ti << vertex([cx+inner_radius*Math.cos(a),cy+inner_radius*Math.sin(a),z1]) to << vertex([cx+outer_radius*Math.cos(a),cy+outer_radius*Math.sin(a),z1]) end segments.times do |i| j=(i+1)%segments quad(oo[i],oo[j],to[j],to[i]); quad(oi[i],ti[i],ti[j],oi[j]) quad(oi[i],oi[j],oo[j],oo[i]); quad(ti[i],to[i],to[j],ti[j]) end end def add_tri_prism(outer, inner) o = outer.map { |p| vertex(p) } i = inner.map { |p| vertex(p) } triangle(o[0], o[1], o[2]); triangle(i[0], i[2], i[1]) 3.times { |n| quad(o[n], o[(n + 1) % 3], i[(n + 1) % 3], i[n]) } end def transform vertices.map! { |point| yield(point) } self end def bounds axes = vertices.transpose axes.map { |axis| [axis.min || 0.0, axis.max || 0.0] } end def valid? !vertices.empty? && triangles.all? { |tri| tri.uniq.length == 3 && tri.all? { |i| i.between?(0, vertices.length - 1) } } end end end