feat: initialize sunrise with 100-player shared rooms
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"""Blender MCP study: shared summit slab, editable outline and summit stone."""
|
||||
import bpy,bmesh,json,math
|
||||
from pathlib import Path
|
||||
from mathutils import Vector
|
||||
ROOT=Path('D:/project/sunrise')
|
||||
OUT=ROOT/'assets/summit/blender-plateau-study'
|
||||
data=json.loads((OUT/'context.json').read_text(encoding='utf-8'))
|
||||
if bpy.data.scenes.get('Summit slab and marker'):
|
||||
raise RuntimeError('Edit the existing study; do not overwrite it by rebuilding.')
|
||||
scene=bpy.data.scenes.new('Summit slab and marker');bpy.context.window.scene=scene
|
||||
collection=bpy.data.collections.new('Summit slab authoring');scene.collection.children.link(collection)
|
||||
def enum(owner,prop,value):
|
||||
return next(i.identifier for i in owner.bl_rna.properties[prop].enum_items if i.identifier==value)
|
||||
def mat(name,color):
|
||||
m=bpy.data.materials.new(name);m.use_nodes=True
|
||||
n=next(n for n in m.node_tree.nodes if n.type=='BSDF_PRINCIPLED')
|
||||
n.inputs['Base Color'].default_value=(*color,1);n.inputs['Roughness'].default_value=.95
|
||||
m.diffuse_color=(*color,1);return m
|
||||
stone=mat('Summit pale weathered granite',(.58,.55,.47))
|
||||
texture=stone.node_tree.nodes.new('ShaderNodeTexImage')
|
||||
texture.image=bpy.data.images.load(str(ROOT/'public/terrain/materials/rock-color.jpg'),check_existing=True)
|
||||
stone.node_tree.links.new(texture.outputs['Color'],next(n for n in stone.node_tree.nodes if n.type=='BSDF_PRINCIPLED').inputs['Base Color'])
|
||||
earth=mat('Plateau transition context',(.19,.24,.14))
|
||||
ink=mat('Recessed summit inscription',(.025,.025,.022))
|
||||
def objmesh(name,verts,faces):
|
||||
mesh=bpy.data.meshes.new(name);mesh.from_pydata(verts,[],faces);mesh.update()
|
||||
obj=bpy.data.objects.new(name,mesh);collection.objects.link(obj);return obj
|
||||
|
||||
# X east, Y north, Z above 620m. Editable boundary is the single source of truth.
|
||||
boundary=[(-17,-59),(-6,-61),(4,-61),(11,-56),(15,-49),(15,-40),(15,-33),
|
||||
(12,-23),(11,-16),(10,-5),(9,6),(2,14),(-7,14),(-12,12),(-18,4),
|
||||
(-22,-4),(-25,-13),(-27,-22),(-26,-31),(-25,-40),(-22,-50)]
|
||||
outline=objmesh('WALKABLE_OUTLINE',[(x,-z,0) for x,z in boundary],[])
|
||||
edges=[(i,(i+1)%len(boundary)) for i in range(len(boundary))]
|
||||
outline.data.clear_geometry();outline.data.from_pydata([(x,-z,0) for x,z in boundary],edges,[])
|
||||
outline.hide_render=True;outline.display_type=enum(bpy.types.Object,'display_type','WIRE')
|
||||
def signed_distance(x,y):
|
||||
hit=False;distance=1e9
|
||||
points=[(a,-b) for a,b in boundary]
|
||||
for i,(ax,ay) in enumerate(points):
|
||||
bx,by=points[(i+1)%len(points)];dx=bx-ax;dy=by-ay
|
||||
t=max(0,min(1,((x-ax)*dx+(y-ay)*dy)/(dx*dx+dy*dy)))
|
||||
distance=min(distance,math.hypot(x-ax-t*dx,y-ay-t*dy))
|
||||
if (ay>y)!=(by>y) and x<(bx-ax)*(y-ay)/(by-ay)+ax:hit=not hit
|
||||
return -distance if hit else distance
|
||||
def slab_height(x,north):
|
||||
# Broad photograph-like shoulder and gentle exposed slope, with shallow joints.
|
||||
ramp=max(-14,min(49,north))*.185
|
||||
shoulder=.30*(math.tanh((north-27)/6)-math.tanh(-27/6))
|
||||
weather=.10*math.sin(x*.25)*math.sin(north*.19)
|
||||
joint=-.12*math.exp(-((x+.18*north+6)/1.1)**2)
|
||||
return ramp+shoulder+weather+joint
|
||||
vertices=[]
|
||||
for x,north,elevation in data['vertices']:
|
||||
d=signed_distance(x,north)
|
||||
t=max(0,min(1,d/7));blend=1-t*t*(3-2*t)
|
||||
height=(elevation-620)*(1-blend)+slab_height(x,north)*blend
|
||||
vertices.append((x,north,height))
|
||||
slab=objmesh('SLAB_SURFACE',vertices,data['faces']);slab.data.materials.append(stone);slab.data.materials.append(earth)
|
||||
uv=slab.data.uv_layers.new(name='Granite UV metres')
|
||||
for face in slab.data.polygons:
|
||||
center=sum((slab.data.vertices[i].co for i in face.vertices),Vector())/len(face.vertices)
|
||||
face.material_index=0 if signed_distance(center.x,center.y)<0 else 1
|
||||
face.use_smooth=True
|
||||
for li in face.loop_indices:
|
||||
p=slab.data.vertices[slab.data.loops[li].vertex_index].co;uv.data[li].uv=(p.x/3,p.y/3)
|
||||
slab['heightDatum']=620
|
||||
slab['note']='App reuses these exact terrain vertices for rendered surface and walking height.'
|
||||
|
||||
root=bpy.data.objects.new('summit-marker',None);collection.objects.link(root)
|
||||
root.location=(-12,22,slab_height(-12,22))
|
||||
root['blockedRadius']=1.1
|
||||
# Irregular broad stone with a cut face for the vertical inscription; no pedestal.
|
||||
front=[(-.82,-.43,-.10),(.76,-.43,-.10),(.65,-.43,1.72),(.35,-.43,2.12),(-.50,-.43,1.94)]
|
||||
back=[(-.65,.40,-.12),(.61,.36,-.12),(.53,.26,1.68),(.28,.17,2.05),(-.40,.28,1.86)]
|
||||
faces=[tuple(reversed(range(5))),tuple(range(5,10))]+[(i,(i+1)%5,(i+1)%5+5,i+5) for i in range(5)]
|
||||
body=objmesh('Marker granite body',front+back,faces);body.parent=root;body.data.materials.append(stone)
|
||||
bpy.context.view_layer.objects.active=body;body.select_set(True)
|
||||
bevel=body.modifiers.new('Weathered stone edges',enum(bpy.types.Modifier,'type','BEVEL'));bevel.width=.07;bevel.segments=2
|
||||
bpy.ops.object.modifier_apply(modifier=bevel.name)
|
||||
bm=bmesh.new();bm.from_mesh(body.data);bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces));bm.to_mesh(body.data);bm.free()
|
||||
uv=body.data.uv_layers.new(name='Stone UV')
|
||||
for face in body.data.polygons:
|
||||
for li in face.loop_indices:
|
||||
p=body.data.vertices[body.data.loops[li].vertex_index].co;uv.data[li].uv=(p.x/1.3,p.z/1.3)
|
||||
font=bpy.data.fonts.load('C:/Windows/Fonts/malgunbd.ttf')
|
||||
for i,letter in enumerate('관악산'):
|
||||
curve=bpy.data.curves.new('Inscription '+letter,next(t.identifier for t in bpy.data.curves.bl_rna.functions['new'].parameters['type'].enum_items if t.identifier=='FONT'))
|
||||
curve.body=letter;curve.font=font;curve.size=.42;curve.extrude=.005
|
||||
curve.align_x=enum(bpy.types.TextCurve,'align_x','CENTER')
|
||||
o=bpy.data.objects.new('Inscription '+letter,curve);collection.objects.link(o);o.parent=root
|
||||
o.location=(0,-.435,1.48-i*.47);o.rotation_euler=(math.pi/2,0,0);curve.materials.append(ink)
|
||||
for other in scene.objects:other.select_set(False)
|
||||
o.select_set(True);bpy.context.view_layer.objects.active=o
|
||||
bpy.ops.object.convert(target=next(t.identifier for t in bpy.ops.object.convert.get_rna_type().properties['target'].enum_items if t.identifier=='MESH'))
|
||||
|
||||
camdata=bpy.data.cameras.new('Plateau inspection camera');cam=bpy.data.objects.new(camdata.name,camdata);collection.objects.link(cam)
|
||||
cam.location=(12,-18,17);cam.rotation_euler=(Vector((-9,23,4))-cam.location).to_track_quat('-Z','Y').to_euler();camdata.lens=38;scene.camera=cam
|
||||
scene.render.resolution_x=1200;scene.render.resolution_y=850;scene.render.resolution_percentage=80
|
||||
scene.world=bpy.data.worlds.new('Plateau daylight');scene.world.use_nodes=True
|
||||
background=next(n for n in scene.world.node_tree.nodes if n.type=='BACKGROUND');background.inputs['Color'].default_value=(.58,.66,.76,1);background.inputs['Strength'].default_value=.7
|
||||
light=bpy.data.lights.new('Slab sun',enum(bpy.types.Light,'type','SUN'));light.energy=2;light.angle=.2
|
||||
sun=bpy.data.objects.new(light.name,light);collection.objects.link(sun);sun.rotation_euler=(.6,-.4,-.5)
|
||||
scene.render.engine='CYCLES';scene.cycles.samples=24
|
||||
bpy.app.driver_namespace['summit_plateau']=globals()
|
||||
print('Blender plateau:',len(vertices),'shared height vertices; boundary:',len(boundary),'points')
|
||||
Reference in New Issue
Block a user