feat: initialize sunrise with 100-player shared rooms
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
"""Run in the existing Blender studies via MCP; preserve walking geometry and facilities.
|
||||
|
||||
Photo-estimated weathering, not photogrammetry. Existing scan maps are reused.
|
||||
"""
|
||||
import bpy, math, json
|
||||
from pathlib import Path
|
||||
from mathutils import Vector, noise
|
||||
|
||||
ROOT = Path('D:/project/sunrise')
|
||||
OUT = ROOT/'assets/summit/near-route-refinement'
|
||||
TILE = 1.8
|
||||
rocks = bpy.data.collections['Photo rocks - export']
|
||||
slab = bpy.data.objects['SLAB_SURFACE']
|
||||
before_slab = [tuple(v.co) for v in slab.data.vertices]
|
||||
report = []
|
||||
|
||||
def joint(p):
|
||||
# Uneven, slightly wandering vertical fractures; avoid horizontal stacked bands.
|
||||
warp = .16*math.sin(p.z*.43+p.y*.27)
|
||||
distance = min(abs(p.x+.16*p.y+.07*p.z+warp-k) for k in (-23.4,-19.1,-14.7,-11.2,-5.2,.8,6.5))
|
||||
return math.exp(-(distance/.30)**2)
|
||||
|
||||
for obj in rocks.objects:
|
||||
if obj.get('route_detail_pass'):
|
||||
raise RuntimeError('Refinement already applied: edit the existing result, do not accumulate.')
|
||||
mesh = obj.data
|
||||
maximum = 0
|
||||
for v in mesh.vertices:
|
||||
p = v.co.copy()
|
||||
fade = max(0,min(1,(p.z+28)/6))
|
||||
# Retain supporting tops and overall silhouette; recess the exposed side faces.
|
||||
side = max(0,1-abs(v.normal.z))
|
||||
amount = -.14*joint(p)*side*fade
|
||||
v.co += v.normal*amount
|
||||
maximum = max(maximum,abs(amount))
|
||||
mesh.update()
|
||||
uv = mesh.uv_layers.active
|
||||
colors = mesh.color_attributes.active_color
|
||||
for face in mesh.polygons:
|
||||
axis = max(range(3),key=lambda a:abs(face.normal[a]))
|
||||
for li in face.loop_indices:
|
||||
p = mesh.vertices[mesh.loops[li].vertex_index].co
|
||||
uv.data[li].uv = Vector((p.y,p.z) if axis==0 else (p.x,p.z) if axis==1 else (p.x,p.y))/TILE
|
||||
patch = noise.noise_vector(p*.26).x
|
||||
stain = max(0,noise.noise_vector(p*.61+Vector((3,9,2))).y)
|
||||
shade = .93+.055*patch-.10*joint(p)-.045*stain
|
||||
colors.data[li].color = (shade,shade*.985,shade*.95,1)
|
||||
obj['route_detail_pass'] = True
|
||||
obj['texture_tile_metres'] = TILE
|
||||
report.append({'object':obj.name,'vertices':len(mesh.vertices),'maxRecessMetres':maximum})
|
||||
|
||||
def setup_material(mat, vertex_colors=False):
|
||||
nodes,links = mat.node_tree.nodes,mat.node_tree.links
|
||||
bsdf = next(n for n in nodes if n.type=='BSDF_PRINCIPLED')
|
||||
color = next(n for n in nodes if n.type=='TEX_IMAGE' and n.image and 'rock-color' in n.image.name)
|
||||
# Use identical two-scale sampling in Blender and the application's ground shader.
|
||||
texcoord = nodes.new('ShaderNodeTexCoord')
|
||||
mapping = nodes.new('ShaderNodeVectorMath');mapping.operation = next(i.identifier for i in mapping.bl_rna.properties['operation'].enum_items if i.identifier=='MULTIPLY_ADD')
|
||||
mapping.inputs[1].default_value=(1.37,1.37,1.37)
|
||||
mapping.inputs[2].default_value=(.31,.73,0)
|
||||
links.new(texcoord.outputs['UV'],mapping.inputs[0])
|
||||
separate=nodes.new('ShaderNodeSeparateXYZ');links.new(texcoord.outputs['UV'],separate.inputs[0])
|
||||
def math_node(operation,a,b=0):
|
||||
node=nodes.new('ShaderNodeMath')
|
||||
node.operation=next(i.identifier for i in node.bl_rna.properties['operation'].enum_items if i.identifier==operation)
|
||||
for socket,value in zip(node.inputs,(a,b)):
|
||||
if isinstance(value,(int,float)):socket.default_value=value
|
||||
else:links.new(value,socket)
|
||||
return node.outputs[0]
|
||||
blend=math_node('ADD',.5,math_node('MULTIPLY',.3,math_node('SINE',math_node('ADD',math_node('MULTIPLY',separate.outputs['X'],.71),math_node('SINE',math_node('MULTIPLY',separate.outputs['Y'],.53))))))
|
||||
def mixed(image, original=None):
|
||||
a = original or nodes.new('ShaderNodeTexImage');a.image=image
|
||||
b = nodes.new('ShaderNodeTexImage');b.image=image
|
||||
links.new(mapping.outputs[0],b.inputs['Vector'])
|
||||
mix=nodes.new('ShaderNodeMixRGB');links.new(blend,mix.inputs[0])
|
||||
links.new(a.outputs['Color'],mix.inputs[1]);links.new(b.outputs['Color'],mix.inputs[2])
|
||||
return mix.outputs[0]
|
||||
base=mixed(color.image,color)
|
||||
if vertex_colors:
|
||||
attr=nodes.new('ShaderNodeVertexColor');attr.layer_name='Weathering'
|
||||
mul=nodes.new('ShaderNodeMixRGB');mul.blend_type=next(i.identifier for i in mul.bl_rna.properties['blend_type'].enum_items if i.identifier=='MULTIPLY');mul.inputs[0].default_value=1
|
||||
links.new(base,mul.inputs[1]);links.new(attr.outputs['Color'],mul.inputs[2]);base=mul.outputs[0]
|
||||
links.new(base,bsdf.inputs['Base Color'])
|
||||
for kind in ('normal','roughness'):
|
||||
img=bpy.data.images.load(str(ROOT/f'public/terrain/materials/rock-{kind}.jpg'),check_existing=True)
|
||||
img.colorspace_settings.name='Non-Color'
|
||||
signal=mixed(img)
|
||||
if kind=='normal':
|
||||
normal=nodes.new('ShaderNodeNormalMap');normal.inputs['Strength'].default_value=.55
|
||||
links.new(signal,normal.inputs['Color']);links.new(normal.outputs['Normal'],bsdf.inputs['Normal'])
|
||||
else:
|
||||
links.new(signal,bsdf.inputs['Roughness'])
|
||||
|
||||
setup_material(bpy.data.materials['Photo study / weathered granite'],True)
|
||||
# Copy the material so the summit marker retains its existing authored finish.
|
||||
surface=slab.data.materials[0].copy();surface.name='Route slab / weathered granite detail'
|
||||
slab.data.materials[0]=surface
|
||||
setup_material(surface)
|
||||
for face in slab.data.polygons:
|
||||
for li in face.loop_indices:
|
||||
p=slab.data.vertices[slab.data.loops[li].vertex_index].co
|
||||
slab.data.uv_layers.active.data[li].uv=(p.x/TILE,p.y/TILE)
|
||||
assert before_slab==[tuple(v.co) for v in slab.data.vertices]
|
||||
(OUT/'refinement.json').write_text(json.dumps({'walkingVerticesUnchanged':True,'tileMetres':TILE,'normalStrength':.55,'rocks':report},indent=2))
|
||||
print('Refined',len(report),'rock meshes; walking surface vertices unchanged.')
|
||||
Reference in New Issue
Block a user