Squash of 25 work commits (Sep 1–12):237da90render: the sprite lane hands the screen draw back the way it found it895b9a7particles: an emitter can ride a body's own frame914471fai-hub: a feed session whose last socket left ends on its idle timeout; skin: parent, skinned centroid and a nodes-only rig for retargets3fcccf3sim: the whole world is implicitly editable, and one seam says where the ground is5692fabsim: the landform world proves itself — walker through the tunnel includedf4af6dfsim: terrain knows who changed it — a plan layer over player historyadbb078render: a water volume can be physics without a picturec17480frender: a non-rigid body may carry its own orientationacad401sim: an agent with no route holds and retries instead of walking into the wall22b2bf9sim + chat: the composed world surface takes a map floor; the chat gets plan toolsfaf8112web path: the tessellator's lap timer, the trace span and the fusion cycle timer have no clock on the webd3472ebtsdf: the clock-taking XR helpers are native-only — the browser has no depth camera and no Instant4946c9esim + render: a repaint is not a world edit — colour and glow restyles never rebake the lightmap4317f58sim + render + chat: walk decks as a surface, the filmed body is no obstruction, the brief never asks9791279render: support rigged models and custom materials across viewers3ce2792sim: use deck geometry for collision and sensing c8b79ff Add portable PBR, rig and soft-body authoring support 108d423 Add transactional polygon modeling and editable asset documents 38f4d3b Refine editable modeling and firm yarn character behavior 08a2637 sim: add entity-owned lights and vehicle headlights 00d96a7 render: add clustered lights, local shadows and incremental GI cf916af render: import glTF asset extensions and wire clustered GI c6d2cca model: cut transaction memory and raise capacity limits 41af47a raytrace: add a CPU probe-ray BVH budget example c963d0a libs: the game sim splits into makepad-scene and makepad-soft-body; render, model, fab and the asset importer retarget Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
31 lines
6.8 KiB
Rust
31 lines
6.8 KiB
Rust
//! Portable fixtures for fresh-client integration of the complete authoring ABI.
|
|
//! cargo run --release -p makepad-model --example authoring_suite -- <directory>
|
|
use makepad_model::*;
|
|
use std::{collections::BTreeMap,path::Path};
|
|
fn apply(d:&mut Document,request:&str,operations:Vec<Operation>)->Result<()>{d.apply(Transaction{request_id:request.into(),expected:d.head(),operations},None)?;Ok(())}
|
|
fn node(object:&str,parent:Option<&str>,position:[f64;3])->Operation{Operation::Scene(SceneOperation::Node{object:object.into(),node:SceneNode{parent:parent.map(str::to_owned),transform:Transform{translation:position,..Default::default()},..Default::default()}})}
|
|
fn material(d:&mut Document,id:u32,color:[f64;4],metallic:f64,roughness:f64,emissive:[f64;3])->Result<()>{
|
|
let mut operations=vec![Operation::SetMaterial{material:id,value:Material::default()},Operation::Surface(SurfaceOperation::Material{material:id,value:SurfaceMaterial{base_color:color,metallic,roughness,emissive,emissive_strength:4.,..Default::default()}})];
|
|
for(channel,pixel)in[(SurfaceChannel::BaseColor,[255;4]),(SurfaceChannel::MetallicRoughness,[255,255,255,255]),(SurfaceChannel::Normal,[128,128,255,255]),(SurfaceChannel::Occlusion,[255;4]),(SurfaceChannel::Emissive,[255;4])]{operations.push(Operation::Surface(SurfaceOperation::Layer{material:id,channel,layer:SurfaceLayer::new("base",RgbaImage::new(8,8,pixel,d.limits())?)}));operations.push(Operation::Surface(SurfaceOperation::Mips{material:id,channel,layer:"base".into()}));}
|
|
apply(d,&format!("material-{id}"),operations)
|
|
}
|
|
fn assign(d:&mut Document,object:&str,id:u32)->Result<()>{let faces=d.object(object).unwrap().faces().iter().map(|f|f.id).collect();apply(d,&format!("paint-{object}"),vec![Operation::AssignMaterial{object:object.into(),faces,material:id}])}
|
|
fn output(d:&Document,name:&str,path:&Path)->Result<()>{let bytes=d.to_bytes(None)?;let round=Document::from_bytes(&bytes,d.limits().clone(),None)?;assert_eq!(round.head(),d.head());let model=round.compile(None)?;
|
|
std::fs::write(path.join(format!("{name}.mpmodel")),bytes).unwrap();std::fs::write(path.join(format!("{name}.glb")),&model.glb).unwrap();println!("{name}: {} triangles, {} vertices, {} material primitives, {} GLB bytes",model.triangles,model.vertices,model.primitives.len(),model.glb.len());Ok(())}
|
|
fn car()->Result<Document>{
|
|
let mut d=Document::new(Limits::default())?;
|
|
apply(&mut d,"body",vec![Operation::Cube{object:"body".into(),size:[1.8,0.6,3.8]},Operation::Cube{object:"cabin".into(),size:[1.55,0.65,1.7]},node("body",None,[0.,0.65,0.]),node("cabin",Some("body"),[0.,0.6,0.25])])?;
|
|
material(&mut d,0,[0.03,0.24,0.65,1.],0.75,0.25,[0.;3])?;material(&mut d,1,[0.015,0.025,0.045,1.],0.25,0.18,[0.;3])?;material(&mut d,2,[0.03,0.03,0.03,1.],0.,0.85,[0.;3])?;material(&mut d,3,[1.,0.95,0.8,1.],0.1,0.12,[1.,0.9,0.6])?;assign(&mut d,"cabin",1)?;
|
|
for(i,(x,z))in[(-0.95,-1.15),(0.95,-1.15),(-0.95,1.15),(0.95,1.15)].into_iter().enumerate(){let name=format!("wheel-{i}");let value=json::parse(format!(r#"[{{"op":"cylinder","object":"{name}","radius":0.38,"height":0.25,"segments":16}}]"#).as_bytes()).unwrap();let mut ops=parse_operations(&value,d.limits())?;ops.push(Operation::Scene(SceneOperation::Node{object:name.clone(),node:SceneNode{parent:Some("body".into()),transform:Transform{translation:[x,-0.27,z],rotation:transform::quat_axis_angle([0.,0.,1.],std::f64::consts::FRAC_PI_2)?,..Default::default()},..Default::default()}}));apply(&mut d,&name,ops)?;assign(&mut d,&name,2)?;}
|
|
for(name,x)in[("headlight-L",-0.62),("headlight-R",0.62)]{apply(&mut d,name,vec![Operation::Cube{object:name.into(),size:[0.42,0.18,0.06]},node(name,Some("body"),[x,0.12,-1.93]),Operation::Scene(SceneOperation::Light(LightEmitter{name:format!("{name}-beam"),attachment:Attachment::Object(name.into()),transform:Transform{translation:[0.,0.,-0.05],..Default::default()},kind:LightKind::Spot{inner:0.15,outer:0.45},color:[1.,0.93,0.78],intensity:900.,range:55.}))])?;assign(&mut d,name,3)?;}
|
|
apply(&mut d,"delivery",vec![Operation::Scene(SceneOperation::Collider{object:"body".into(),proxy:CollisionProxy::Box}),Operation::Scene(SceneOperation::Socket(Socket{name:"driver".into(),attachment:Attachment::Object("body".into()),transform:Transform{translation:[0.35,0.6,0.2],..Default::default()}}))])?;Ok(d)
|
|
}
|
|
fn character()->Result<Document>{
|
|
let mut d=Document::new(Limits::default())?;apply(&mut d,"body",vec![Operation::Cube{object:"body".into(),size:[0.65,1.,0.3]},Operation::Cube{object:"hand".into(),size:[0.2,0.65,0.22]},node("body",None,[0.,0.7,0.]),node("hand",None,[0.5,0.9,0.])])?;
|
|
material(&mut d,0,[0.05,0.6,0.5,1.],0.2,0.45,[0.;3])?;material(&mut d,1,[0.95,0.4,0.06,1.],0.5,0.25,[0.04,0.01,0.])?;assign(&mut d,"hand",1)?;
|
|
apply(&mut d,"rig",vec![Operation::SetSkeleton{skeleton:Skeleton{joints:vec![Joint{name:"root".into(),parent:None,translation:[0.,0.7,0.]},Joint{name:"shoulder".into(),parent:Some(0),translation:[0.5,0.5,0.]}]}},Operation::Rig(RigOperation::Rest{joint:1,transform:Transform{translation:[0.5,0.5,0.],rotation:transform::quat_axis_angle([0.,1.,0.],0.12)?,..Default::default()}})])?;
|
|
for(object,joint)in[("body",0),("hand",1)]{let ops=d.object(object).unwrap().vertices().iter().map(|v|Operation::SetWeights{object:object.into(),vertex:v.id,weights:vec![mesh::JointWeight{joint,weight:1.}]}).collect();apply(&mut d,&format!("bind-{object}"),ops)?;}
|
|
let deltas=d.object("body").unwrap().vertices().iter().map(|v|(v.id,[v.position[0]*0.1,0.,0.])).collect();
|
|
apply(&mut d,"animation",vec![Operation::Rig(RigOperation::Morph{name:"breath".into(),object:"body".into(),deltas,weight:0.1}),Operation::SetClip{clip:AnimationClip{name:"wave".into(),channels:vec![AnimationChannel{joint:1,path:AnimationPath::Rotation,keys:vec![Keyframe{time:0.,value:[0.,0.,0.,1.]},Keyframe{time:0.5,value:transform::quat_axis_angle([0.,0.,1.],1.)?},Keyframe{time:1.,value:[0.,0.,0.,1.]}]}]}},Operation::Rig(RigOperation::ClipOptions{name:"wave".into(),options:ClipOptions{events:vec![ClipEvent{time:0.5,name:"greeting".into(),payload:"hand raised".into()}],morph_keys:BTreeMap::from([("breath".into(),vec![MorphKey{time:0.,weight:0.},MorphKey{time:0.5,weight:1.},MorphKey{time:1.,weight:0.}])]),..Default::default()}}),Operation::Scene(SceneOperation::Light(LightEmitter{name:"wrist-lamp".into(),attachment:Attachment::Joint(1),transform:Transform{translation:[0.,-0.65,-0.15],..Default::default()},kind:LightKind::Spot{inner:0.2,outer:0.5},color:[0.5,0.8,1.],intensity:40.,range:8.})),Operation::Scene(SceneOperation::Socket(Socket{name:"held-tool".into(),attachment:Attachment::Joint(1),transform:Transform{translation:[0.,-0.65,0.],..Default::default()}}))])?;Ok(d)
|
|
}
|
|
fn main()->std::result::Result<(),Box<dyn std::error::Error>>{let path=std::env::args_os().nth(1).ok_or("supply output directory")?;let path=std::path::PathBuf::from(path);std::fs::create_dir_all(&path)?;output(&car()?,"car",&path)?;output(&character()?,"character",&path)?;Ok(())}
|