billboards
This commit is contained in:
parent
544c44ca31
commit
b85f225789
10 changed files with 1750 additions and 46 deletions
314
tests/demo/screens/billboard_building_demo.py
Normal file
314
tests/demo/screens/billboard_building_demo.py
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
# billboard_building_demo.py - Visual demo of Billboard and Mesh Instances
|
||||
# Demonstrates camera-facing sprites and static mesh placement
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
import math
|
||||
|
||||
# Create demo scene
|
||||
scene = mcrfpy.Scene("billboard_building_demo")
|
||||
|
||||
# Dark background frame
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(15, 15, 25))
|
||||
scene.children.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Billboard & Building Demo - 3D Sprites and Static Meshes", pos=(20, 10))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
scene.children.append(title)
|
||||
|
||||
# Create the 3D viewport
|
||||
viewport = mcrfpy.Viewport3D(
|
||||
pos=(50, 60),
|
||||
size=(600, 450),
|
||||
render_resolution=(320, 240), # PS1 resolution
|
||||
fov=60.0,
|
||||
camera_pos=(16.0, 10.0, 20.0),
|
||||
camera_target=(8.0, 0.0, 8.0),
|
||||
bg_color=mcrfpy.Color(80, 120, 180) # Sky blue background
|
||||
)
|
||||
scene.children.append(viewport)
|
||||
|
||||
# Set up the navigation grid
|
||||
GRID_SIZE = 16
|
||||
viewport.set_grid_size(GRID_SIZE, GRID_SIZE)
|
||||
|
||||
# Generate terrain
|
||||
print("Generating terrain...")
|
||||
hm = mcrfpy.HeightMap((GRID_SIZE, GRID_SIZE))
|
||||
hm.mid_point_displacement(0.2, seed=456) # Gentle terrain
|
||||
hm.normalize(0.0, 0.5) # Keep it low for placing objects
|
||||
|
||||
# Apply heightmap
|
||||
viewport.apply_heightmap(hm, 2.0)
|
||||
|
||||
# Build terrain mesh
|
||||
vertex_count = viewport.build_terrain(
|
||||
layer_name="terrain",
|
||||
heightmap=hm,
|
||||
y_scale=2.0,
|
||||
cell_size=1.0
|
||||
)
|
||||
print(f"Terrain built with {vertex_count} vertices")
|
||||
|
||||
# Create terrain colors (earthy tones)
|
||||
r_map = mcrfpy.HeightMap((GRID_SIZE, GRID_SIZE))
|
||||
g_map = mcrfpy.HeightMap((GRID_SIZE, GRID_SIZE))
|
||||
b_map = mcrfpy.HeightMap((GRID_SIZE, GRID_SIZE))
|
||||
|
||||
for y in range(GRID_SIZE):
|
||||
for x in range(GRID_SIZE):
|
||||
h = hm[x, y]
|
||||
# Earth/grass colors
|
||||
r_map[x, y] = 0.25 + h * 0.2
|
||||
g_map[x, y] = 0.35 + h * 0.25
|
||||
b_map[x, y] = 0.15 + h * 0.1
|
||||
|
||||
viewport.apply_terrain_colors("terrain", r_map, g_map, b_map)
|
||||
|
||||
# =============================================================================
|
||||
# PART 1: Building Placement using Mesh Instances
|
||||
# =============================================================================
|
||||
print("Placing buildings...")
|
||||
|
||||
# Add a layer for buildings
|
||||
viewport.add_layer("buildings", z_index=1)
|
||||
|
||||
# Create a simple building model (cube-like structure)
|
||||
building_model = mcrfpy.Model3D()
|
||||
|
||||
# Place several buildings at different locations with transforms
|
||||
building_positions = [
|
||||
((2, 0, 2), 0, 1.5), # Position, rotation, scale
|
||||
((12, 0, 2), 45, 1.2),
|
||||
((4, 0, 12), 90, 1.0),
|
||||
((10, 0, 10), 30, 1.8),
|
||||
]
|
||||
|
||||
for pos, rotation, scale in building_positions:
|
||||
idx = viewport.add_mesh("buildings", building_model, pos=pos, rotation=rotation, scale=scale)
|
||||
print(f" Placed building {idx} at {pos}")
|
||||
|
||||
# Mark the footprint as blocking
|
||||
gx, gz = int(pos[0]), int(pos[2])
|
||||
footprint_size = max(1, int(scale))
|
||||
viewport.place_blocking(grid_pos=(gx, gz), footprint=(footprint_size, footprint_size))
|
||||
|
||||
print(f"Placed {len(building_positions)} buildings")
|
||||
|
||||
# =============================================================================
|
||||
# PART 2: Billboard Sprites (camera-facing)
|
||||
# =============================================================================
|
||||
print("Creating billboards...")
|
||||
|
||||
# Create billboards for "trees" - camera_y mode (stays upright)
|
||||
tree_positions = [
|
||||
(3, 0, 5), (5, 0, 3), (6, 0, 8), (9, 0, 5),
|
||||
(11, 0, 7), (7, 0, 11), (13, 0, 13), (1, 0, 9)
|
||||
]
|
||||
|
||||
# Note: Without actual textures, billboards will render as simple quads
|
||||
# In a real game, you'd load a tree sprite texture
|
||||
for i, pos in enumerate(tree_positions):
|
||||
bb = mcrfpy.Billboard(
|
||||
pos=pos,
|
||||
scale=1.5,
|
||||
facing="camera_y", # Stays upright, only rotates on Y axis
|
||||
opacity=1.0
|
||||
)
|
||||
viewport.add_billboard(bb)
|
||||
|
||||
print(f" Created {len(tree_positions)} tree billboards (camera_y facing)")
|
||||
|
||||
# Create some particle-like billboards - full camera facing
|
||||
particle_positions = [
|
||||
(8, 3, 8), (8.5, 3.5, 8.2), (7.5, 3.2, 7.8), # Floating particles
|
||||
]
|
||||
|
||||
for i, pos in enumerate(particle_positions):
|
||||
bb = mcrfpy.Billboard(
|
||||
pos=pos,
|
||||
scale=0.3,
|
||||
facing="camera", # Full rotation to face camera
|
||||
opacity=0.7
|
||||
)
|
||||
viewport.add_billboard(bb)
|
||||
|
||||
print(f" Created {len(particle_positions)} particle billboards (camera facing)")
|
||||
|
||||
# Create a fixed-orientation billboard (signpost)
|
||||
signpost = mcrfpy.Billboard(
|
||||
pos=(5, 1.5, 5),
|
||||
scale=1.0,
|
||||
facing="fixed", # Manual orientation
|
||||
)
|
||||
signpost.theta = math.pi / 4 # 45 degrees horizontal
|
||||
signpost.phi = 0.0 # No vertical tilt
|
||||
viewport.add_billboard(signpost)
|
||||
|
||||
print(f" Created 1 signpost billboard (fixed facing)")
|
||||
print(f"Total billboards: {viewport.billboard_count()}")
|
||||
|
||||
# =============================================================================
|
||||
# Info Panel
|
||||
# =============================================================================
|
||||
info_panel = mcrfpy.Frame(pos=(670, 60), size=(330, 450),
|
||||
fill_color=mcrfpy.Color(30, 30, 40),
|
||||
outline_color=mcrfpy.Color(80, 80, 100),
|
||||
outline=2.0)
|
||||
scene.children.append(info_panel)
|
||||
|
||||
# Panel title
|
||||
panel_title = mcrfpy.Caption(text="Billboard & Mesh Demo", pos=(690, 70))
|
||||
panel_title.fill_color = mcrfpy.Color(200, 200, 255)
|
||||
scene.children.append(panel_title)
|
||||
|
||||
# Billboard info
|
||||
bb_info = [
|
||||
("", ""),
|
||||
("Billboard Modes:", ""),
|
||||
(" camera", "Full rotation to face camera"),
|
||||
(" camera_y", "Y-axis only (stays upright)"),
|
||||
(" fixed", "Manual theta/phi angles"),
|
||||
("", ""),
|
||||
(f"Trees:", f"{len(tree_positions)} (camera_y)"),
|
||||
(f"Particles:", f"{len(particle_positions)} (camera)"),
|
||||
(f"Signpost:", "1 (fixed)"),
|
||||
("", ""),
|
||||
("Mesh Instances:", ""),
|
||||
(f" Buildings:", f"{len(building_positions)}"),
|
||||
]
|
||||
|
||||
y_offset = 100
|
||||
for label, value in bb_info:
|
||||
if label or value:
|
||||
text = f"{label} {value}" if value else label
|
||||
cap = mcrfpy.Caption(text=text, pos=(690, y_offset))
|
||||
cap.fill_color = mcrfpy.Color(150, 150, 170)
|
||||
scene.children.append(cap)
|
||||
y_offset += 22
|
||||
|
||||
# Dynamic camera info
|
||||
camera_label = mcrfpy.Caption(text="Camera: Following...", pos=(690, y_offset + 20))
|
||||
camera_label.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
scene.children.append(camera_label)
|
||||
|
||||
# Instructions at bottom
|
||||
instructions = mcrfpy.Caption(
|
||||
text="[Space] Toggle orbit | [1-3] Change billboard mode | [C] Clear buildings | [ESC] Quit",
|
||||
pos=(20, 530)
|
||||
)
|
||||
instructions.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
scene.children.append(instructions)
|
||||
|
||||
# Status line
|
||||
status = mcrfpy.Caption(text="Status: Billboard & Building demo loaded", pos=(20, 555))
|
||||
status.fill_color = mcrfpy.Color(100, 200, 100)
|
||||
scene.children.append(status)
|
||||
|
||||
# Animation state
|
||||
animation_time = [0.0]
|
||||
camera_orbit = [True]
|
||||
|
||||
# Update function
|
||||
def update(timer, runtime):
|
||||
animation_time[0] += runtime / 1000.0
|
||||
|
||||
# Camera orbit
|
||||
if camera_orbit[0]:
|
||||
angle = animation_time[0] * 0.3
|
||||
radius = 18.0
|
||||
center_x = 8.0
|
||||
center_z = 8.0
|
||||
height = 10.0 + math.sin(animation_time[0] * 0.2) * 2.0
|
||||
|
||||
x = center_x + math.cos(angle) * radius
|
||||
z = center_z + math.sin(angle) * radius
|
||||
|
||||
viewport.camera_pos = (x, height, z)
|
||||
viewport.camera_target = (center_x, 1.0, center_z)
|
||||
|
||||
camera_label.text = f"Camera: Orbit ({x:.1f}, {height:.1f}, {z:.1f})"
|
||||
|
||||
# Animate particle billboards (bobbing up and down)
|
||||
bb_count = viewport.billboard_count()
|
||||
if bb_count > len(tree_positions):
|
||||
particle_start = len(tree_positions)
|
||||
for i in range(particle_start, particle_start + len(particle_positions)):
|
||||
if i < bb_count:
|
||||
bb = viewport.get_billboard(i)
|
||||
pos = bb.pos
|
||||
new_y = 3.0 + math.sin(animation_time[0] * 2.0 + i * 0.5) * 0.5
|
||||
bb.pos = (pos[0], new_y, pos[2])
|
||||
|
||||
# Key handler
|
||||
def on_key(key, state):
|
||||
if state != mcrfpy.InputState.PRESSED:
|
||||
return
|
||||
|
||||
if key == mcrfpy.Key.SPACE:
|
||||
camera_orbit[0] = not camera_orbit[0]
|
||||
status.text = f"Camera orbit: {'ON' if camera_orbit[0] else 'OFF'}"
|
||||
|
||||
elif key == mcrfpy.Key.NUM_1:
|
||||
# Change all tree billboards to "camera" mode
|
||||
for i in range(len(tree_positions)):
|
||||
viewport.get_billboard(i).facing = "camera"
|
||||
status.text = "Trees now use 'camera' facing (full rotation)"
|
||||
|
||||
elif key == mcrfpy.Key.NUM_2:
|
||||
# Change all tree billboards to "camera_y" mode
|
||||
for i in range(len(tree_positions)):
|
||||
viewport.get_billboard(i).facing = "camera_y"
|
||||
status.text = "Trees now use 'camera_y' facing (upright)"
|
||||
|
||||
elif key == mcrfpy.Key.NUM_3:
|
||||
# Change all tree billboards to "fixed" mode
|
||||
for i in range(len(tree_positions)):
|
||||
bb = viewport.get_billboard(i)
|
||||
bb.facing = "fixed"
|
||||
bb.theta = i * 0.5 # Different angles
|
||||
status.text = "Trees now use 'fixed' facing (manual angles)"
|
||||
|
||||
elif key == mcrfpy.Key.C:
|
||||
viewport.clear_meshes("buildings")
|
||||
status.text = "Cleared all buildings from layer"
|
||||
|
||||
elif key == mcrfpy.Key.O:
|
||||
# Adjust tree opacity
|
||||
for i in range(len(tree_positions)):
|
||||
bb = viewport.get_billboard(i)
|
||||
bb.opacity = 0.5 if bb.opacity > 0.7 else 1.0
|
||||
status.text = f"Tree opacity toggled"
|
||||
|
||||
elif key == mcrfpy.Key.V:
|
||||
# Toggle tree visibility
|
||||
for i in range(len(tree_positions)):
|
||||
bb = viewport.get_billboard(i)
|
||||
bb.visible = not bb.visible
|
||||
status.text = f"Tree visibility toggled"
|
||||
|
||||
elif key == mcrfpy.Key.ESCAPE:
|
||||
mcrfpy.exit()
|
||||
|
||||
# Set up scene
|
||||
scene.on_key = on_key
|
||||
|
||||
# Create timer for updates
|
||||
timer = mcrfpy.Timer("billboard_update", update, 16) # ~60fps
|
||||
|
||||
# Activate scene
|
||||
mcrfpy.current_scene = scene
|
||||
|
||||
print()
|
||||
print("Billboard & Building Demo loaded!")
|
||||
print()
|
||||
print("Controls:")
|
||||
print(" [Space] Toggle camera orbit")
|
||||
print(" [1] Trees -> 'camera' facing")
|
||||
print(" [2] Trees -> 'camera_y' facing (default)")
|
||||
print(" [3] Trees -> 'fixed' facing")
|
||||
print(" [O] Toggle tree opacity")
|
||||
print(" [V] Toggle tree visibility")
|
||||
print(" [C] Clear buildings")
|
||||
print(" [ESC] Quit")
|
||||
Loading…
Add table
Add a link
Reference in a new issue