Module: Quake::Physics::HullTrace
- Defined in:
- lib/quake/physics/hull_trace.rb
Class Method Summary collapse
-
.hull0_clipnodes(level) ⇒ Object
Quake's Mod_MakeHull0: duplicate the render node tree as clipnodes so point traces (hull 0) can reuse the same recursive hull check.
- .hull_for_box(mins, maxs) ⇒ Object
-
.point_contents(clipnodes, planes, node_index, point) ⇒ Object
Check what content type a point is in, using the clipnode tree.
-
.trace(clipnodes, planes, node_index, p1, p2) ⇒ Object
Trace a line from p1 to p2 through the hull.
-
.trace_world_and_entities(level, p1, p2, brush_entities, hull_num: 1) ⇒ Object
Trace against the world AND all solid brush entities, returning the nearest hit.
Class Method Details
.hull0_clipnodes(level) ⇒ Object
Quake's Mod_MakeHull0: duplicate the render node tree as clipnodes so point traces (hull 0) can reuse the same recursive hull check. A node child < 0 encodes leaf -(child + 1); hull 0 replaces it with the leaf contents, exactly like the C loader.
102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/quake/physics/hull_trace.rb', line 102 def self.hull0_clipnodes(level) return @hull0_clipnodes if @hull0_level&.equal?(level) @hull0_level = level @hull0_clipnodes = level.nodes.map do |node| children = node.children.map do |child| child >= 0 ? child : level.leafs[-child - 1].contents end Quake::Bsp::ClipNode.new(plane_index: node.plane_index, children: children) end end |
.hull_for_box(mins, maxs) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/quake/physics/hull_trace.rb', line 28 def self.hull_for_box(mins, maxs) clipnodes = 6.times.map do |i| side = i & 1 children = Array.new(2) children[side] = CONTENTS_EMPTY children[side ^ 1] = i == 5 ? CONTENTS_SOLID : i + 1 Quake::Bsp::ClipNode.new(plane_index: i, children: children) end planes = [ box_plane(type: 0, dist: maxs.x), box_plane(type: 0, dist: mins.x), box_plane(type: 1, dist: maxs.y), box_plane(type: 1, dist: mins.y), box_plane(type: 2, dist: maxs.z), box_plane(type: 2, dist: mins.z) ] BoxHull.new( clipnodes: clipnodes, planes: planes, first_clipnode: 0, last_clipnode: 5 ) end |
.point_contents(clipnodes, planes, node_index, point) ⇒ Object
Check what content type a point is in, using the clipnode tree. hull_clipnodes: array of ClipNode planes: array of Plane node_index: starting clipnode index point: Vec3
58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/quake/physics/hull_trace.rb', line 58 def self.point_contents(clipnodes, planes, node_index, point) while node_index >= 0 if node_index >= clipnodes.size raise "SV_HullPointContents: bad node number" end node = clipnodes[node_index] plane = planes[node.plane_index] dist = plane_distance(plane, point) node_index = dist >= 0 ? node.children[0] : node.children[1] end node_index # negative value = content type end |
.trace(clipnodes, planes, node_index, p1, p2) ⇒ Object
Trace a line from p1 to p2 through the hull. Returns a TraceResult.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/quake/physics/hull_trace.rb', line 75 def self.trace(clipnodes, planes, node_index, p1, p2) result = { all_solid: false, start_solid: false, in_open: false, in_water: false, fraction: 1.0, end_pos: p2, plane_normal: nil, plane_dist: nil } recursive_trace(clipnodes, planes, node_index, 0.0, 1.0, p1, p2, result) if result[:fraction] == 1.0 result[:end_pos] = p2 else result[:end_pos] = Math::Vec3.new( p1.x + result[:fraction] * (p2.x - p1.x), p1.y + result[:fraction] * (p2.y - p1.y), p1.z + result[:fraction] * (p2.z - p1.z) ) end TraceResult.new(**result) end |
.trace_world_and_entities(level, p1, p2, brush_entities, hull_num: 1) ⇒ Object
Trace against the world AND all solid brush entities, returning the nearest hit. Brush entity traces are done in entity-local space (subtract origin, trace sub-model hull, transform back). hull_num 0 is a point trace through the render-node hull, matching Quake's SV_TraceLine with MOVE_NOMONSTERS (world + SOLID_BSP only).
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/quake/physics/hull_trace.rb', line 119 def self.trace_world_and_entities(level, p1, p2, brush_entities, hull_num: 1) clipnodes = hull_num.zero? ? hull0_clipnodes(level) : level.clipnodes planes = level.planes # World trace world_hull = level.models[0].head_nodes[hull_num] world_hull = level.models[0].head_nodes[0] if world_hull < 0 || world_hull >= clipnodes.size best = trace(clipnodes, planes, world_hull, p1, p2) # Trace against each brush entity's sub-model brush_entities&.each do |ent| next unless ent.brush_entity? next if ent.removed? # Triggers are SOLID_TRIGGER in Quake: they detect overlap but # don't block movement. They're handled by check_triggers. next if ent.classname.start_with?("trigger_") next if ent.classname == "func_illusionary" model = level.models[ent.model_index] next unless model sub_hull = model.head_nodes[hull_num] # Many sub-models only have hull 0; fall back gracefully sub_hull = model.head_nodes[0] if sub_hull < 0 || sub_hull >= clipnodes.size next if sub_hull < 0 || sub_hull >= clipnodes.size # Transform trace into entity-local space offset = ent.position local_p1 = Math::Vec3.new(p1.x - offset.x, p1.y - offset.y, p1.z - offset.z) local_p2 = Math::Vec3.new(p2.x - offset.x, p2.y - offset.y, p2.z - offset.z) sub_result = trace(clipnodes, planes, sub_hull, local_p1, local_p2) # Keep nearest hit if sub_result.fraction < best.fraction # Transform end_pos back to world space ep = sub_result.end_pos best = TraceResult.new( all_solid: sub_result.all_solid, start_solid: sub_result.start_solid, in_open: sub_result.in_open, in_water: sub_result.in_water, fraction: sub_result.fraction, end_pos: Math::Vec3.new(ep.x + offset.x, ep.y + offset.y, ep.z + offset.z), plane_normal: sub_result.plane_normal, plane_dist: sub_result.plane_dist ) end end best end |