tostido's picture
Add blueprints archive: ARACHNE-001, MARIONETTE-001, AIRFOIL-CORDAGE-SYSTEM, PERSPECTIVE
26fa66a
"""
Enhanced Visual Geometry for KAPS
==================================
Proper 3D geometry for:
- CABLES: Thick tubes with tension coloring, not thin lines
- AIRFOILS: Visible delta wings with thickness and control surfaces
- BUZZARD: Detailed mother drone with features
- THREATS: Distinctive shapes per threat type
All geometry is procedural Panda3D compatible.
"""
import numpy as np
from typing import Tuple, List
try:
from panda3d.core import (
GeomVertexFormat, GeomVertexData, GeomVertexWriter,
Geom, GeomTriangles, GeomTristrips, GeomNode,
Vec3, Vec4, Point3,
LineSegs
)
PANDA3D_AVAILABLE = True
except ImportError:
PANDA3D_AVAILABLE = False
def create_cable_geometry(
start: np.ndarray,
end: np.ndarray,
radius: float = 0.5,
segments: int = 8,
color: Tuple[float, float, float, float] = (0.4, 0.4, 0.5, 1.0),
tension_color: Tuple[float, float, float, float] = None
) -> GeomNode:
"""
Create a 3D tube/cable geometry between two points.
This creates a visible CYLINDER, not a thin line.
Args:
start: Start position
end: End position
radius: Cable thickness (default 0.5m)
segments: Number of sides (8 = octagon cross-section)
color: Base color
tension_color: Color at end if cable is under tension
"""
if not PANDA3D_AVAILABLE:
return None
format = GeomVertexFormat.getV3n3c4()
vdata = GeomVertexData("cable", format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, "vertex")
normal = GeomVertexWriter(vdata, "normal")
col = GeomVertexWriter(vdata, "color")
# Direction vector
direction = end - start
length = np.linalg.norm(direction)
if length < 0.01:
length = 0.01
direction = direction / length
# Create perpendicular vectors for circular cross-section
if abs(direction[2]) < 0.9:
perp1 = np.cross(direction, np.array([0, 0, 1]))
else:
perp1 = np.cross(direction, np.array([1, 0, 0]))
perp1 = perp1 / np.linalg.norm(perp1)
perp2 = np.cross(direction, perp1)
# Create vertices at start and end caps
for t, pos, c in [(0, start, color), (1, end, tension_color or color)]:
for i in range(segments + 1):
angle = 2 * np.pi * i / segments
offset = perp1 * np.cos(angle) * radius + perp2 * np.sin(angle) * radius
p = pos + offset
n = offset / radius
vertex.addData3f(p[0], p[1], p[2])
normal.addData3f(n[0], n[1], n[2])
col.addData4f(*c)
# Create triangles connecting the two rings
prim = GeomTriangles(Geom.UHStatic)
for i in range(segments):
# Start ring vertex indices
s0 = i
s1 = i + 1
# End ring vertex indices
e0 = segments + 1 + i
e1 = segments + 1 + i + 1
# Two triangles per quad
prim.addVertices(s0, e0, s1)
prim.addVertices(s1, e0, e1)
geom = Geom(vdata)
geom.addPrimitive(prim)
node = GeomNode("cable")
node.addGeom(geom)
return node
def create_airfoil_geometry(
wingspan: float = 4.0,
chord: float = 1.5,
thickness: float = 0.3,
color: Tuple[float, float, float, float] = (0.8, 0.8, 0.8, 1.0),
highlight_color: Tuple[float, float, float, float] = None
) -> GeomNode:
"""
Create a detailed flying wing / delta airfoil geometry.
This creates a VISIBLE 3D wing shape with:
- Proper delta planform
- Visible thickness
- Leading and trailing edges
- Control surface hints
Args:
wingspan: Total wing span (tip to tip)
chord: Root chord length
thickness: Maximum thickness
color: Main body color
highlight_color: Leading edge accent color
"""
if not PANDA3D_AVAILABLE:
return None
format = GeomVertexFormat.getV3n3c4()
vdata = GeomVertexData("airfoil", format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, "vertex")
normal = GeomVertexWriter(vdata, "normal")
col = GeomVertexWriter(vdata, "color")
half_span = wingspan / 2
tip_chord = chord * 0.3 # Wingtips are narrower
# Leading edge color
le_color = highlight_color or (
min(1.0, color[0] + 0.2),
min(1.0, color[1] + 0.2),
min(1.0, color[2] + 0.2),
color[3]
)
# Define airfoil cross-section points (normalized)
# This gives a proper airfoil shape
airfoil_top = [
(0.0, 0.0), # Leading edge
(0.1, 0.04), # Near LE
(0.3, 0.06), # Max thickness
(0.6, 0.04), # Aft
(1.0, 0.01), # Trailing edge
]
airfoil_bottom = [
(0.0, 0.0), # Leading edge
(0.1, -0.02), # Near LE
(0.3, -0.03), # Max thickness
(0.6, -0.02), # Aft
(1.0, -0.01), # Trailing edge
]
# Scale factor for thickness
t_scale = thickness * 10
# Create wing vertices
# Center section (root)
sections = [
(0, chord, color), # Root
(half_span * 0.3, chord * 0.8, color), # Mid-span
(half_span * 0.6, chord * 0.5, color), # Outer
(half_span, tip_chord, le_color), # Tip
]
verts = []
norms = []
colors = []
for y_pos, local_chord, c in sections:
for side in [1, -1]: # Right and left wing
y = y_pos * side
# Top surface
for x_frac, z_frac in airfoil_top:
x = -local_chord * x_frac + local_chord * 0.3 # Offset so LE is forward
z = z_frac * t_scale
verts.append((x, y, z))
norms.append((0, 0, 1)) # Simplified up normal
colors.append(c)
# Bottom surface
for x_frac, z_frac in airfoil_bottom:
x = -local_chord * x_frac + local_chord * 0.3
z = z_frac * t_scale
verts.append((x, y, z))
norms.append((0, 0, -1)) # Simplified down normal
colors.append(c)
# Add all vertices
for v, n, c in zip(verts, norms, colors):
vertex.addData3f(*v)
normal.addData3f(*n)
col.addData4f(*c)
# Create a simpler but more visible geometry
# Just make a thick delta wing shape
vdata2 = GeomVertexData("airfoil2", format, Geom.UHStatic)
vertex2 = GeomVertexWriter(vdata2, "vertex")
normal2 = GeomVertexWriter(vdata2, "normal")
col2 = GeomVertexWriter(vdata2, "color")
# Simpler delta wing - 14 vertices
simple_verts = [
# Top surface
(chord * 0.5, 0, thickness), # 0: Nose top
(-chord * 0.3, half_span, thickness/2), # 1: Right tip top
(-chord * 0.3, -half_span, thickness/2), # 2: Left tip top
(-chord * 0.5, 0, thickness/2), # 3: Tail center top
# Bottom surface
(chord * 0.5, 0, -thickness/2), # 4: Nose bottom
(-chord * 0.3, half_span, -thickness/2), # 5: Right tip bottom
(-chord * 0.3, -half_span, -thickness/2), # 6: Left tip bottom
(-chord * 0.5, 0, -thickness/2), # 7: Tail center bottom
# Leading edge points (for thickness)
(chord * 0.4, half_span * 0.3, 0), # 8: Right LE mid
(chord * 0.4, -half_span * 0.3, 0), # 9: Left LE mid
# Mid-span ridge (for wing shape)
(0, half_span * 0.5, thickness), # 10: Right ridge
(0, -half_span * 0.5, thickness), # 11: Left ridge
(0, half_span * 0.5, -thickness/3), # 12: Right ridge bottom
(0, -half_span * 0.5, -thickness/3), # 13: Left ridge bottom
]
simple_norms = [
(0, 0, 1), (0.2, 0.5, 0.8), (0.2, -0.5, 0.8), (0, 0, 1), # Top
(0, 0, -1), (0.2, 0.5, -0.8), (0.2, -0.5, -0.8), (0, 0, -1), # Bottom
(0.7, 0.7, 0), (0.7, -0.7, 0), # LE
(0, 0.3, 0.95), (0, -0.3, 0.95), # Ridge top
(0, 0.3, -0.95), (0, -0.3, -0.95), # Ridge bottom
]
for v, n in zip(simple_verts, simple_norms):
vertex2.addData3f(*v)
normal2.addData3f(*n)
col2.addData4f(*color)
# Triangle faces
prim2 = GeomTriangles(Geom.UHStatic)
# Top surface - 4 triangles
prim2.addVertices(0, 10, 11) # Nose to ridges
prim2.addVertices(0, 11, 2) # Nose to left tip
prim2.addVertices(0, 1, 10) # Nose to right tip
prim2.addVertices(10, 3, 11) # Ridges to tail
prim2.addVertices(10, 1, 3) # Right aft
prim2.addVertices(11, 3, 2) # Left aft
# Bottom surface - 4 triangles (reversed winding)
prim2.addVertices(4, 13, 12)
prim2.addVertices(4, 6, 13)
prim2.addVertices(4, 12, 5)
prim2.addVertices(12, 13, 7)
prim2.addVertices(12, 7, 5)
prim2.addVertices(13, 6, 7)
# Leading edges (sides)
prim2.addVertices(0, 8, 4) # Nose front
prim2.addVertices(0, 4, 9) # Nose front other
prim2.addVertices(0, 1, 8) # Right LE
prim2.addVertices(8, 1, 5)
prim2.addVertices(8, 5, 4)
prim2.addVertices(0, 9, 2) # Left LE
prim2.addVertices(9, 6, 2)
prim2.addVertices(9, 4, 6)
# Trailing edge
prim2.addVertices(3, 1, 5)
prim2.addVertices(3, 5, 7)
prim2.addVertices(3, 2, 6)
prim2.addVertices(3, 6, 7)
geom2 = Geom(vdata2)
geom2.addPrimitive(prim2)
node = GeomNode("airfoil")
node.addGeom(geom2)
return node
def create_buzzard_geometry(
body_length: float = 8.0,
body_radius: float = 2.0,
color: Tuple[float, float, float, float] = (0.2, 0.3, 0.8, 1.0)
) -> GeomNode:
"""
Create detailed Buzzard (mother drone) geometry.
The Buzzard is the PROTECTED ASSET - it should be visually prominent.
Features:
- Elongated fuselage
- Corkscrew propulsion housing (rear)
- Sensor dome (front)
- TAB attachment points
"""
if not PANDA3D_AVAILABLE:
return None
format = GeomVertexFormat.getV3n3c4()
vdata = GeomVertexData("buzzard", format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, "vertex")
normal = GeomVertexWriter(vdata, "normal")
col = GeomVertexWriter(vdata, "color")
segments = 16 # Around circumference
length_segments = 8 # Along length
# Fuselage profile (radius at each length station)
profile = [
(0.0, 0.3), # Nose
(0.1, 0.6), # Nose blend
(0.2, 0.85), # Forward
(0.4, 1.0), # Max radius
(0.6, 1.0), # Max radius
(0.8, 0.85), # Aft taper
(0.9, 0.6), # Engine housing
(1.0, 0.4), # Tail
]
# Accent colors
nose_color = (0.5, 0.6, 0.9, 1.0) # Lighter nose
engine_color = (0.3, 0.3, 0.4, 1.0) # Darker engine
# Generate vertices
for i, (t, r_factor) in enumerate(profile):
x = body_length * (t - 0.5) # Center at 0
r = body_radius * r_factor
# Color gradient
if t < 0.2:
c = nose_color
elif t > 0.8:
c = engine_color
else:
c = color
for j in range(segments + 1):
angle = 2 * np.pi * j / segments
y = r * np.cos(angle)
z = r * np.sin(angle)
# Normal points outward
n = np.array([0, np.cos(angle), np.sin(angle)])
vertex.addData3f(x, y, z)
normal.addData3f(n[0], n[1], n[2])
col.addData4f(*c)
# Create triangles
prim = GeomTriangles(Geom.UHStatic)
for i in range(len(profile) - 1):
for j in range(segments):
# Current ring
v0 = i * (segments + 1) + j
v1 = i * (segments + 1) + j + 1
# Next ring
v2 = (i + 1) * (segments + 1) + j
v3 = (i + 1) * (segments + 1) + j + 1
prim.addVertices(v0, v2, v1)
prim.addVertices(v1, v2, v3)
geom = Geom(vdata)
geom.addPrimitive(prim)
node = GeomNode("buzzard")
node.addGeom(geom)
return node
def create_threat_geometry(
threat_type: str = "missile",
size: float = 2.0,
color: Tuple[float, float, float, float] = (1.0, 0.2, 0.1, 1.0)
) -> GeomNode:
"""
Create threat-specific geometry.
Different shapes for different threat types:
- missile: Elongated cylinder with fins
- drone: Quad-copter shape
- swarm: Small sphere
"""
if not PANDA3D_AVAILABLE:
return None
format = GeomVertexFormat.getV3n3c4()
vdata = GeomVertexData(threat_type, format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, "vertex")
normal = GeomVertexWriter(vdata, "normal")
col = GeomVertexWriter(vdata, "color")
if threat_type in ["missile", "IR_MISSILE", "RADAR_MISSILE"]:
# Elongated cone/cylinder
length = size * 2
radius = size * 0.3
segments = 8
# Nose cone
vertex.addData3f(length/2, 0, 0)
normal.addData3f(1, 0, 0)
col.addData4f(*color)
# Body
for i in range(segments + 1):
angle = 2 * np.pi * i / segments
y = radius * np.cos(angle)
z = radius * np.sin(angle)
# Nose ring
vertex.addData3f(length/4, y * 0.5, z * 0.5)
normal.addData3f(0.5, np.cos(angle) * 0.5, np.sin(angle) * 0.5)
col.addData4f(*color)
# Body ring
vertex.addData3f(-length/4, y, z)
normal.addData3f(0, np.cos(angle), np.sin(angle))
col.addData4f(*color)
# Tail
vertex.addData3f(-length/2, y * 0.7, z * 0.7)
normal.addData3f(-0.3, np.cos(angle) * 0.7, np.sin(angle) * 0.7)
col.addData4f(color[0] * 0.5, color[1] * 0.5, color[2] * 0.5, 1)
prim = GeomTriangles(Geom.UHStatic)
# Nose cone triangles
for i in range(segments):
prim.addVertices(0, 1 + i * 3, 1 + (i + 1) * 3)
# Body triangles
for i in range(segments):
n = 1 + i * 3
nn = 1 + ((i + 1) % (segments + 1)) * 3
# Nose to body
prim.addVertices(n, n + 1, nn)
prim.addVertices(nn, n + 1, nn + 1)
# Body to tail
prim.addVertices(n + 1, n + 2, nn + 1)
prim.addVertices(nn + 1, n + 2, nn + 2)
else:
# Simple sphere for other threats
segments = 8
for i in range(segments + 1):
lat = np.pi * (-0.5 + float(i) / segments)
for j in range(segments + 1):
lon = 2 * np.pi * float(j) / segments
x = size * np.cos(lat) * np.cos(lon)
y = size * np.cos(lat) * np.sin(lon)
z = size * np.sin(lat)
vertex.addData3f(x, y, z)
normal.addData3f(x/size, y/size, z/size)
col.addData4f(*color)
prim = GeomTriangles(Geom.UHStatic)
for i in range(segments):
for j in range(segments):
v0 = i * (segments + 1) + j
v1 = v0 + 1
v2 = v0 + segments + 1
v3 = v2 + 1
prim.addVertices(v0, v2, v1)
prim.addVertices(v1, v2, v3)
geom = Geom(vdata)
geom.addPrimitive(prim)
node = GeomNode(threat_type)
node.addGeom(geom)
return node
# =============================================================================
# TAB COLORS (consistent across system)
# =============================================================================
TAB_COLORS = {
"UP": (0.1, 0.9, 0.2, 1.0), # Bright green - above
"DOWN": (0.9, 0.1, 0.1, 1.0), # Red - below
"LEFT": (0.9, 0.9, 0.1, 1.0), # Yellow - port
"RIGHT": (0.9, 0.1, 0.9, 1.0), # Magenta - starboard
}
CABLE_COLORS = {
"normal": (0.5, 0.5, 0.6, 1.0),
"tension_low": (0.4, 0.6, 0.4, 1.0),
"tension_high": (0.8, 0.4, 0.2, 1.0),
"near_limit": (1.0, 0.2, 0.1, 1.0),
}