diff --git a/barmesh/draw.gd b/barmesh/draw.gd index 45a9f4a..f249d51 100644 --- a/barmesh/draw.gd +++ b/barmesh/draw.gd @@ -189,7 +189,8 @@ func _draw_barmesh(bm: BarMesh) -> void: continue if nd.contact_normal.length_squared() < 1e-12: continue - var p0: Vector3 = _draw_pt(nd) + # Normals start at mesh contact; bars use cutter CL (fixed XY). + var p0: Vector3 = nd.contact_point var p1: Vector3 = p0 + nd.contact_normal * tick nim.surface_add_vertex(cad_to_godot(p0)) nim.surface_add_vertex(cad_to_godot(p1)) @@ -200,6 +201,6 @@ func _draw_barmesh(bm: BarMesh) -> void: func _draw_pt(nd: BarMesh.BMNode) -> Vector3: - if nd.contact_kind != BarMesh.BMNode.ContactFeature.NONE: - return nd.contact_point + ## Lattice = cutter locations. Drop is along Z: CL keeps input (x, y). + ## Do not draw at contact_point — tilted faces shift contact XY and break overhead regularity. return nd.p diff --git a/barmesh/tool_contact.gd b/barmesh/tool_contact.gd index 8ef9fdb..045d69a 100644 --- a/barmesh/tool_contact.gd +++ b/barmesh/tool_contact.gd @@ -109,6 +109,8 @@ static func drop_tool_contact(x: float, y: float, radius: float, tris_cad: Array hit_pt = face["point"] if not found: return { + "x": x, + "y": y, "z": fallback_z, "kind": 0, "tri": -1, @@ -126,7 +128,10 @@ static func drop_tool_contact(x: float, y: float, radius: float, tris_cad: Array hit_pt = cl - n * radius elif n.length_squared() < 1e-12: n = Vector3(0, 0, 1) + ## CL is always (probe_x, probe_y, z). Contact point may differ in XY on tilted faces. return { + "x": x, + "y": y, "z": z_best, "kind": kind, "tri": tri_i, diff --git a/scripts/orbit_camera.gd b/scripts/orbit_camera.gd index a95ab03..73bd0ab 100644 --- a/scripts/orbit_camera.gd +++ b/scripts/orbit_camera.gd @@ -66,7 +66,8 @@ func _unhandled_input(event: InputEvent) -> void: var mm := event as InputEventMouseMotion if _orbiting: _yaw -= mm.relative.x * sensitivity - _pitch = clampf(_pitch - mm.relative.y * sensitivity, 0.05, 1.4) + # Allow true overhead (π/2) like Godot Editor top view; keep a tiny floor so we can orbit out. + _pitch = clampf(_pitch - mm.relative.y * sensitivity, 0.02, PI * 0.5) _apply() get_viewport().set_input_as_handled() elif _panning: @@ -96,4 +97,8 @@ func _apply() -> void: _distance * cos(_pitch) * cos(_yaw) ) global_position = _focus + offset - look_at(_focus, Vector3.UP) + # look_at(..., UP) fails / flips near straight-down; use a stable up near overhead. + var up := Vector3.UP + if _pitch > PI * 0.5 - 0.08: + up = Vector3(-sin(_yaw), 0.0, -cos(_yaw)) + look_at(_focus, up) diff --git a/tools/test_contact.gd b/tools/test_contact.gd index cc173ee..c799c77 100644 --- a/tools/test_contact.gd +++ b/tools/test_contact.gd @@ -21,6 +21,12 @@ func _near(a: float, b: float, eps: float = 1e-5) -> bool: return absf(a - b) <= eps +func _assert_cl_xy(hit: Dictionary, x: float, y: float) -> void: + ## Drop along +Z tool axis: returned (x,y) must equal the probe. + if not _near(float(hit["x"]), x) or not _near(float(hit["y"]), y): + _fail("CL xy drifted probe=(%s,%s) hit=(%s,%s)" % [x, y, hit["x"], hit["y"]]) + + func _run() -> void: var a := Vector3(0, 0, 0) var b := Vector3(0.02, 0, 0) @@ -32,13 +38,14 @@ func _run() -> void: if int(face["kind"]) != KIND_FACE: _fail("face kind " + str(face)) return + _assert_cl_xy(face, 0.005, 0.005) if not _near(float(face["z"]), R): _fail("face CL z " + str(face["z"])) return if absf(face["point"].z) > 1e-5: _fail("face point not on plane") return - var cl_f := Vector3(0.005, 0.005, float(face["z"])) + var cl_f := Vector3(float(face["x"]), float(face["y"]), float(face["z"])) if absf(cl_f.distance_to(face["point"]) - R) > 1e-4: _fail("face |CL-pt| != R") return @@ -47,6 +54,7 @@ func _run() -> void: if int(vert["kind"]) != KIND_VERTEX: _fail("vertex kind " + str(vert)) return + _assert_cl_xy(vert, 0.0, 0.0) if vert["point"].distance_to(a) > 1e-6: _fail("vertex point") return @@ -58,6 +66,7 @@ func _run() -> void: if int(edge["kind"]) != KIND_EDGE: _fail("edge kind " + str(edge)) return + _assert_cl_xy(edge, 0.01, 0.0) if absf(edge["point"].y) > 1e-6 or absf(edge["point"].z) > 1e-5: _fail("edge point not on ab") return @@ -65,5 +74,24 @@ func _run() -> void: _fail("edge |CL-pt| != R") return - print("CONTACT_OK face,vertex,edge R=", R) + # Tilted face: contact XY can move; cutter CL must still keep probe (x,y). + var tilt: Array = [ + [Vector3(0, 0, 0), Vector3(0.02, 0, 0), Vector3(0, 0.02, 0.01)], + ] + var tilt_hit: Dictionary = Contact.drop_tool_contact(0.006, 0.006, R, tilt, -1.0) + _assert_cl_xy(tilt_hit, 0.006, 0.006) + if int(tilt_hit["kind"]) == 0: + _fail("tilt no contact") + return + var cl_t := Vector3(float(tilt_hit["x"]), float(tilt_hit["y"]), float(tilt_hit["z"])) + if absf(cl_t.distance_to(tilt_hit["point"]) - R) > 1e-4: + _fail("tilt |CL-pt| != R") + return + # On a tilt, contact point should generally differ from CL in XY (else face was flat). + var dxy: float = Vector2(cl_t.x - tilt_hit["point"].x, cl_t.y - tilt_hit["point"].y).length() + if dxy < 1e-6 and int(tilt_hit["kind"]) == KIND_FACE: + _fail("tilt face contact XY unexpected equal to CL") + return + + print("CONTACT_OK face,vertex,edge,tilt_xy R=", R) quit(0)