adjust the stand a bit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class StandSurfaceTest < Minitest::Test
|
||||
def test_fuller_blends_remove_port_creases_without_thinning_or_moving_feet
|
||||
[[6.0,6.0,6.0], [6.0,4.56,4.56]].each do |widths|
|
||||
mesh, surface, ports, ends = junction_fixture(widths)
|
||||
before = Marshal.load(Marshal.dump(mesh))
|
||||
before_angles = port_angles(before, ports)
|
||||
before_volume = surface.signed_volume
|
||||
surface.fair_junctions!
|
||||
surface.validate!
|
||||
after_angles = port_angles(mesh, ports)
|
||||
|
||||
assert_operator after_angles.max, :<, before_angles.max * 0.40
|
||||
# A 48-sided profile has 7.5-degree facets; compare actual flat-shaded
|
||||
# triangle normals, allowing that discretization but not the old crease.
|
||||
assert_operator after_angles.max, :<, 10.0, "avoid sharp creases across the arm openings"
|
||||
collars = ports.map { |port| { ring: port[:collar_rings].last } }
|
||||
assert_operator port_angles(mesh, collars).max, :<, 1.0, "the blend must taper smoothly into the unchanged rail"
|
||||
assert_operator surface.signed_volume, :>, before_volume
|
||||
assert mesh.vertices.each_with_index.all? { |p,i| p.first(2) == before.vertices[i].first(2) }, "preserve the footprint and routes"
|
||||
assert before.vertices.each_with_index.all? { |p,i| p[2] > 1e-7 || mesh.vertices[i] == p }, "hold every table-contact vertex fixed"
|
||||
assert ends.flatten.all? { |id| mesh.vertices[id] == before.vertices[id] }, "outer collar boundaries must stay fixed"
|
||||
sides = surface.instance_variable_get(:@sides)
|
||||
assert mesh.vertices.each_with_index.all? { |p,i| (p[2]-before.vertices[i][2])*sides.fetch(i,0) >= -1e-9 }, "only add thickness"
|
||||
assert_equal before.triangles, mesh.triangles, "retain the connected topology"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def junction_fixture(widths)
|
||||
mesh = MoonModel::Mesh.new(name: "junction_fixture")
|
||||
surface = MoonModel::StandSurface.new(mesh, 48, 48)
|
||||
section = ->(_x,_y,q,z) { [z + (q < 0 ? 3.75 : 3.0)*q, 0.0].max }
|
||||
junction = { center: [0.0,0.0,3.0], section: section, ports: [] }
|
||||
ends = []
|
||||
widths.each_with_index do |width,i|
|
||||
angle = i*2*Math::PI/3
|
||||
port = surface.port(junction, angle, 6.0, width)
|
||||
junction[:ports] << port
|
||||
finish = { center: [18*Math.cos(angle),18*Math.sin(angle),3.0], section: section }
|
||||
terminal = surface.port(finish, angle+Math::PI, 0, width)
|
||||
surface.rail(port, terminal, width, 128, section)
|
||||
ends << terminal[:ring]
|
||||
center = mesh.vertex(finish[:center])
|
||||
48.times { |j| mesh.triangle(center, terminal[:ring][j], terminal[:ring][(j+1)%48]) }
|
||||
end
|
||||
surface.junction(junction)
|
||||
surface.orient!
|
||||
[mesh, surface, junction[:ports], ends]
|
||||
end
|
||||
|
||||
def port_angles(mesh, ports)
|
||||
geometry = MoonModel::StandSurface.new(mesh, 48, 24)
|
||||
edges = geometry.edge_faces
|
||||
ports.flat_map do |port|
|
||||
# Exclude the silhouette and intentional flat-bottom edges.
|
||||
(2...22).map do |index|
|
||||
ids = [port[:ring][index],port[:ring][index+1]].minmax
|
||||
normals = edges.fetch(ids).map do |face,_|
|
||||
n = geometry.normal(mesh.triangles[face])
|
||||
length = Math.sqrt(n.sum { |v| v*v })
|
||||
n.map { |v| v/length }
|
||||
end
|
||||
cosine = normals[0].zip(normals[1]).sum { |a,b| a*b }
|
||||
Math.acos([[cosine,-1.0].max,1.0].min)*180/Math::PI
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user