mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
mod API: text, turning parts and copycats in block entity drawing
- native_draw_text(draw, text, at, color, glowing, outline): a Text under a block entity's pose as a sign draws its lines (Font.drawInBatch in a BlockEntityRenderer), each span in its colour, glowing text full bright, an optional outline; it returns the width. The sign's glyph quads are shared (block_entity_render::text_vertices). - BlockEntityDraw::spin(axis, pivot, degrees_per_tick, phase) and still(): parts that turn by themselves. The game turns them each frame from the game time and partial tick, so with keep() a shaft at a steady speed is drawn again with no call of native_block_entity_submit until its block entity changes (Flywheel's rotating instances, on the CPU). - native_block_face_sprite(state, side): the texture a block state shows on a side, by the name native_draw_cube takes, for copycat blocks. Connected textures need no code: a content mod ships OptiFine CTM rules under assets/<id>/optifine/ctm/, as the docs now say. Pending fragments: draw_text.json, spinning_geometry.json, block_face_sprite.json. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
429b0d0f25
commit
ccac8f85ff
7 files changed
+338
-13
No files matched your search
@@ -695,11 +695,23 @@ const TEXT_OVER_OUTLINE: f32 = 0.05;
|
||||
/// reader instead (a twentieth of a font pixel, invisible) rather than
|
||||
/// z-fighting with the outline under it.
|
||||
fn draw_text(scene: &Scene, call: &TextCall, out: &mut Vec<Vertex>) {
|
||||
text_vertices(scene.glyph, call, out);
|
||||
}
|
||||
|
||||
/// `draw_text` with the font's glyph cells and advances from `glyph`: the
|
||||
/// text of `call` as glyph quads in its matrix's plane, 1 unit a font pixel,
|
||||
/// y down as the font's, with its outline when it has one. A native mod's
|
||||
/// block entity draws its text this way (`native_draw_text`).
|
||||
pub fn text_vertices(
|
||||
glyph: &dyn Fn(char) -> (Option<Rect>, f32),
|
||||
call: &TextCall,
|
||||
out: &mut Vec<Vertex>,
|
||||
) {
|
||||
let mut emit = |dx: f32, dy: f32, z: f32, argb: u32| {
|
||||
let mut x = call.x + dx;
|
||||
let y0 = call.y + dy;
|
||||
for ch in call.text.chars() {
|
||||
let (cell, advance) = (scene.glyph)(ch);
|
||||
let (cell, advance) = glyph(ch);
|
||||
if let Some(r) = cell {
|
||||
let corners = [(x, y0 + 8.0), (x + 8.0, y0 + 8.0), (x + 8.0, y0), (x, y0)];
|
||||
let uvs = [[r.u0, r.v1], [r.u1, r.v1], [r.u1, r.v0], [r.u0, r.v0]];
|
||||
|
||||
@@ -11,13 +11,14 @@ use horizon_assets::{
|
||||
model::{Direction, Material},
|
||||
};
|
||||
use horizon_client::{
|
||||
block_entity_render::{diffuse, diffuse_lights, vertex_light},
|
||||
block_entity_render::{TextCall, diffuse, diffuse_lights, text_vertices, vertex_light},
|
||||
containers::{BlockEntity, ModRevision},
|
||||
fast_hash::FxHashMap,
|
||||
gfx::Cull,
|
||||
hud::Rect,
|
||||
items::ItemStack,
|
||||
mesher::Vertex,
|
||||
text::Text,
|
||||
};
|
||||
use horizon_world::registry::BlockStateId;
|
||||
|
||||
@@ -58,6 +59,76 @@ pub struct BlockEntityDraw {
|
||||
lights: (Vec3, Vec3),
|
||||
vertices: Vec<Vertex>,
|
||||
kept: bool,
|
||||
/// Where each turning part starts in `vertices` (`spin`), and where the
|
||||
/// parts standing still start again (`still`, none).
|
||||
spins: Vec<(usize, Option<Spin>)>,
|
||||
}
|
||||
|
||||
/// A part of a block entity's frame that turns by itself (`BlockEntityDraw::spin`).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
struct Spin {
|
||||
axis: Vec3,
|
||||
pivot: Vec3,
|
||||
degrees_per_tick: f32,
|
||||
phase: f32,
|
||||
}
|
||||
|
||||
impl Spin {
|
||||
/// The turn at `game_time` plus `partial`, about the pivot, in the
|
||||
/// block's coordinates. Counted in f64: a world's game time has more
|
||||
/// ticks than an f32 holds exactly.
|
||||
fn turn(&self, game_time: i64, partial: f32) -> Mat4 {
|
||||
let t = game_time as f64 + f64::from(partial);
|
||||
let degrees =
|
||||
(t * f64::from(self.degrees_per_tick) + f64::from(self.phase)).rem_euclid(360.0);
|
||||
Mat4::from_translation(self.pivot)
|
||||
* Mat4::from_quat(Quat::from_axis_angle(
|
||||
self.axis,
|
||||
(degrees as f32).to_radians(),
|
||||
))
|
||||
* Mat4::from_translation(-self.pivot)
|
||||
}
|
||||
}
|
||||
|
||||
/// `vertices`, drawn relative to the block, moved to the camera by `origin`
|
||||
/// and each part of `spins` (starts relative to `vertices`) turned as it is
|
||||
/// at `game_time` plus `partial`, onto `out`.
|
||||
fn place_turning(
|
||||
vertices: &[Vertex],
|
||||
spins: &[(usize, Option<Spin>)],
|
||||
game_time: i64,
|
||||
partial: f32,
|
||||
origin: Vec3,
|
||||
out: &mut Vec<Vertex>,
|
||||
) {
|
||||
let mut turn: Option<Mat4> = None;
|
||||
let mut next = spins.iter().peekable();
|
||||
for (i, v) in vertices.iter().enumerate() {
|
||||
while let Some((start, spin)) = next.next_if(|(start, _)| *start <= i) {
|
||||
let _ = start;
|
||||
turn = spin.map(|s| s.turn(game_time, partial));
|
||||
}
|
||||
let mut v = *v;
|
||||
if let Some(m) = turn {
|
||||
v.pos = m.transform_point3(Vec3::from_array(v.pos)).to_array();
|
||||
if v.normal[3] != 0 {
|
||||
let n = Vec3::new(
|
||||
f32::from(v.normal[0]),
|
||||
f32::from(v.normal[1]),
|
||||
f32::from(v.normal[2]),
|
||||
);
|
||||
let n = m.transform_vector3(n);
|
||||
v.normal = [
|
||||
n.x.round().clamp(-127.0, 127.0) as i8,
|
||||
n.y.round().clamp(-127.0, 127.0) as i8,
|
||||
n.z.round().clamp(-127.0, 127.0) as i8,
|
||||
v.normal[3],
|
||||
];
|
||||
}
|
||||
}
|
||||
v.pos = (Vec3::from_array(v.pos) + origin).to_array();
|
||||
out.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -83,6 +154,7 @@ impl BlockEntityDraw {
|
||||
lights,
|
||||
vertices,
|
||||
kept: false,
|
||||
spins: Vec::new(),
|
||||
}
|
||||
}
|
||||
/// The vertices drawn, with those it was made with first.
|
||||
@@ -126,13 +198,44 @@ impl BlockEntityDraw {
|
||||
/// its block entity (what the server sent), its light or the resource
|
||||
/// packs change. Call it only when what you drew reads none of
|
||||
/// `partial`, `game_time`, the blocks around it or anything else of
|
||||
/// yours, and moves nothing by itself: an idle machine, not a turning
|
||||
/// shaft. Kept geometry stands still: an enchanted item's glint, and an
|
||||
/// yours, and moves nothing by itself but through `spin`: an idle machine,
|
||||
/// or a shaft turning at a steady speed, not one whose speed changes from
|
||||
/// frame to frame. Kept geometry stands still: an enchanted item's glint, and an
|
||||
/// item drawn with `native_draw_item` whose model reads the time (a clock,
|
||||
/// a compass), stop where they were. API 94.
|
||||
pub fn keep(&mut self) {
|
||||
self.kept = true;
|
||||
}
|
||||
/// What is drawn next turns by itself: `degrees_per_tick` degrees each
|
||||
/// game tick (a shaft at `rpm` turns `rpm * 0.3`, as Create's do), about
|
||||
/// `axis` through `pivot`, both in blocks from the block's corner
|
||||
/// whatever the pose, counterclockwise seen from the axis' positive end,
|
||||
/// `phase` degrees round at game time 0 (to mesh a cog with its
|
||||
/// neighbour's). Draw the part at rest; the game turns it each frame from
|
||||
/// the game time and the partial tick. With `keep` it goes on turning
|
||||
/// with no call of `native_block_entity_submit` until what it was drawn
|
||||
/// with changes: send a new speed in the update tag and the block is
|
||||
/// drawn again (Flywheel's rotating instances, done on the CPU). Until
|
||||
/// `still` or the next `spin`. A block entity's frame only: a mob's
|
||||
/// (`native_mob_submit`) draws it at rest. API @API:spinning_geometry@.
|
||||
pub fn spin(&mut self, axis: [f32; 3], pivot: [f32; 3], degrees_per_tick: f32, phase: f32) {
|
||||
let spin = Spin {
|
||||
axis: Vec3::from_array(axis).normalize_or(Vec3::Y),
|
||||
pivot: Vec3::from_array(pivot),
|
||||
degrees_per_tick: if degrees_per_tick.is_finite() {
|
||||
degrees_per_tick
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
phase: if phase.is_finite() { phase } else { 0.0 },
|
||||
};
|
||||
self.spins.push((self.vertices.len(), Some(spin)));
|
||||
}
|
||||
/// What is drawn next stands still again, after `spin`.
|
||||
/// API @API:spinning_geometry@.
|
||||
pub fn still(&mut self) {
|
||||
self.spins.push((self.vertices.len(), None));
|
||||
}
|
||||
fn apply(&mut self, m: Mat4) {
|
||||
if let Some(pose) = self.poses.last_mut() {
|
||||
*pose *= m;
|
||||
@@ -206,6 +309,8 @@ struct KeptEntry {
|
||||
used: i64,
|
||||
/// Relative to the block's corner.
|
||||
vertices: Vec<Vertex>,
|
||||
/// Its turning parts (`BlockEntityDraw::spin`), from the start of `vertices`.
|
||||
spins: Vec<(usize, Option<Spin>)>,
|
||||
}
|
||||
|
||||
/// Geometry mods kept (`BlockEntityDraw::keep`), by position. As
|
||||
@@ -250,6 +355,7 @@ impl KeptGeometry {
|
||||
revision: ModRevision,
|
||||
bounds: [f32; 6],
|
||||
vertices: &[Vertex],
|
||||
spins: Vec<(usize, Option<Spin>)>,
|
||||
now: i64,
|
||||
) {
|
||||
let mut kept = self
|
||||
@@ -266,6 +372,7 @@ impl KeptGeometry {
|
||||
bounds,
|
||||
used: now,
|
||||
vertices: kept,
|
||||
spins,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -314,6 +421,7 @@ impl Game<'_> {
|
||||
lights: diffuse_lights(self.dim == Dim::Nether),
|
||||
vertices: std::mem::take(out),
|
||||
kept: false,
|
||||
spins: Vec::new(),
|
||||
};
|
||||
let now = self.world.game_time;
|
||||
let mut kept = self.kept_block_entities.borrow_mut();
|
||||
@@ -340,10 +448,21 @@ impl Game<'_> {
|
||||
if in_frustum(self.view_frustum.as_ref(), origin, entry.bounds) {
|
||||
let start = draw.vertices.len();
|
||||
self.mark_block_entity(start, state);
|
||||
draw.vertices.extend(entry.vertices.iter().map(|v| Vertex {
|
||||
pos: (Vec3::from_array(v.pos) + origin).to_array(),
|
||||
..*v
|
||||
}));
|
||||
if entry.spins.is_empty() {
|
||||
draw.vertices.extend(entry.vertices.iter().map(|v| Vertex {
|
||||
pos: (Vec3::from_array(v.pos) + origin).to_array(),
|
||||
..*v
|
||||
}));
|
||||
} else {
|
||||
place_turning(
|
||||
&entry.vertices,
|
||||
&entry.spins,
|
||||
draw.game_time,
|
||||
draw.partial,
|
||||
origin,
|
||||
&mut draw.vertices,
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -363,7 +482,14 @@ impl Game<'_> {
|
||||
draw.poses.push(Mat4::IDENTITY);
|
||||
draw.light = light;
|
||||
draw.kept = false;
|
||||
draw.spins.clear();
|
||||
self.native_block_entity_submit(pos, block, id, entity, &mut draw);
|
||||
// The turning parts, from the start of this block's vertices.
|
||||
let spins: Vec<(usize, Option<Spin>)> = draw
|
||||
.spins
|
||||
.drain(..)
|
||||
.map(|(at, spin)| (at.saturating_sub(start), spin))
|
||||
.collect();
|
||||
if draw.kept {
|
||||
kept.keep(
|
||||
pos,
|
||||
@@ -371,13 +497,26 @@ impl Game<'_> {
|
||||
entity.revision(),
|
||||
bounds,
|
||||
&draw.vertices[start..],
|
||||
spins.clone(),
|
||||
now,
|
||||
);
|
||||
} else {
|
||||
kept.forget(pos);
|
||||
}
|
||||
for v in &mut draw.vertices[start..] {
|
||||
v.pos = (Vec3::from_array(v.pos) + origin).to_array();
|
||||
if spins.is_empty() {
|
||||
for v in &mut draw.vertices[start..] {
|
||||
v.pos = (Vec3::from_array(v.pos) + origin).to_array();
|
||||
}
|
||||
} else {
|
||||
let drawn = draw.vertices.split_off(start);
|
||||
place_turning(
|
||||
&drawn,
|
||||
&spins,
|
||||
draw.game_time,
|
||||
draw.partial,
|
||||
origin,
|
||||
&mut draw.vertices,
|
||||
);
|
||||
}
|
||||
}
|
||||
*out = draw.vertices;
|
||||
@@ -415,6 +554,51 @@ impl Game<'_> {
|
||||
true
|
||||
}
|
||||
|
||||
/// Draws `text` under `draw`'s pose as a sign draws its lines
|
||||
/// (`Font.drawInBatch` in a `BlockEntityRenderer`): in the pose's x-y
|
||||
/// plane, one unit a font pixel, y down, its left end at `at`, facing
|
||||
/// +z. Scale the pose first as a sign does (`draw.scale([1.0 / 64.0,
|
||||
/// -1.0 / 64.0, 1.0 / 64.0])` for its letters, 8 pixels high). Each span
|
||||
/// is drawn in its own colour, the others in the 0xRRGGBB `color`;
|
||||
/// `glowing` text is full bright (a glow ink sac's), the rest lit by
|
||||
/// `draw.light`; a nonzero 0xRRGGBB `outline` rings each glyph, as glowing
|
||||
/// sign text's. Returns the width drawn, in font pixels: centre a line
|
||||
/// by starting it at minus half its width. API @API:draw_text@.
|
||||
pub(super) fn native_draw_text(
|
||||
&self,
|
||||
draw: &mut BlockEntityDraw,
|
||||
text: &Text,
|
||||
at: [f32; 2],
|
||||
color: u32,
|
||||
glowing: bool,
|
||||
outline: u32,
|
||||
) -> f32 {
|
||||
let glyph = |c: char| self.gui.atlas.glyph_cell(c);
|
||||
let light = if glowing { 240 | 240 << 8 } else { draw.light };
|
||||
let matrix = draw.pose();
|
||||
let mut x = at[0];
|
||||
for span in &text.spans {
|
||||
let shown = horizon_client::bidi::visual_str(&span.text).into_owned();
|
||||
let width: f32 = shown.chars().map(|c| glyph(c).1).sum();
|
||||
let call = TextCall {
|
||||
matrix,
|
||||
x,
|
||||
y: at[1],
|
||||
text: shown,
|
||||
color: 0xFF00_0000 | (span.style.color.unwrap_or(color) & 0xFF_FFFF),
|
||||
outline: if outline == 0 {
|
||||
0
|
||||
} else {
|
||||
0xFF00_0000 | (outline & 0xFF_FFFF)
|
||||
},
|
||||
light,
|
||||
};
|
||||
text_vertices(&glyph, &call, &mut draw.vertices);
|
||||
x += width;
|
||||
}
|
||||
x - at[0]
|
||||
}
|
||||
|
||||
/// Draws a box from `from` to `to` (texels of 16 per block, as a block
|
||||
/// model's element) under `draw`'s pose, with the faces given, by
|
||||
/// `Direction` (down, up, north, south, west, east), baked as a model
|
||||
@@ -578,6 +762,7 @@ mod tests {
|
||||
lights: diffuse_lights(false),
|
||||
vertices: Vec::new(),
|
||||
kept: false,
|
||||
spins: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,6 +869,55 @@ mod tests {
|
||||
light: 0xF0F0,
|
||||
};
|
||||
|
||||
/// A part drawn after `spin` turns about its pivot by the game time and
|
||||
/// partial tick (90 degrees a tick about +y takes +x to -z, by the right
|
||||
/// hand), its normal with it; what is drawn after `still` and before the
|
||||
/// spin stays; all of it is moved by the origin.
|
||||
#[test]
|
||||
fn a_spinning_part_turns_with_the_game_time_and_the_rest_stays() {
|
||||
let mut draw = draw();
|
||||
let at = |x: f32, y: f32, z: f32, normal: [i8; 4]| Vertex {
|
||||
pos: [x, y, z],
|
||||
normal,
|
||||
..bytemuck::Zeroable::zeroed()
|
||||
};
|
||||
draw.vertices.push(at(1.0, 0.5, 0.5, [0; 4]));
|
||||
draw.spin([0.0, 1.0, 0.0], [0.5, 0.5, 0.5], 90.0, 0.0);
|
||||
draw.vertices.push(at(1.0, 0.5, 0.5, [127, 0, 0, 127]));
|
||||
draw.still();
|
||||
draw.vertices.push(at(0.0, 0.0, 0.0, [0; 4]));
|
||||
let origin = Vec3::new(10.0, 0.0, 0.0);
|
||||
let close = |a: [f32; 3], b: [f32; 3]| a.iter().zip(b).all(|(a, b)| (a - b).abs() < 1e-4);
|
||||
// A tick and a half: 135 degrees.
|
||||
let mut out = Vec::new();
|
||||
place_turning(&draw.vertices, &draw.spins, 1, 0.5, origin, &mut out);
|
||||
assert!(close(out[0].pos, [11.0, 0.5, 0.5]), "{:?}", out[0].pos);
|
||||
let h = 0.5 * std::f32::consts::FRAC_1_SQRT_2;
|
||||
assert!(
|
||||
close(out[1].pos, [10.5 - h, 0.5, 0.5 - h]),
|
||||
"{:?}",
|
||||
out[1].pos
|
||||
);
|
||||
assert!(close(out[2].pos, [10.0, 0.0, 0.0]), "{:?}", out[2].pos);
|
||||
// One tick: a quarter turn, +x to -z, the normal along.
|
||||
out.clear();
|
||||
place_turning(&draw.vertices, &draw.spins, 1, 0.0, origin, &mut out);
|
||||
assert!(close(out[1].pos, [10.5, 0.5, 0.0]), "{:?}", out[1].pos);
|
||||
assert_eq!(out[1].normal, [0, 0, -127, 127]);
|
||||
// Four ticks: all the way round, and a world's late game time is no
|
||||
// less exact.
|
||||
out.clear();
|
||||
place_turning(
|
||||
&draw.vertices,
|
||||
&draw.spins,
|
||||
4_000_000_000,
|
||||
0.0,
|
||||
origin,
|
||||
&mut out,
|
||||
);
|
||||
assert!(close(out[1].pos, [11.0, 0.5, 0.5]), "{:?}", out[1].pos);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kept_geometry_is_drawn_again_until_what_it_was_drawn_with_changes() {
|
||||
let mut kept = KeptGeometry::default();
|
||||
@@ -694,7 +928,15 @@ mod tests {
|
||||
..Default::default()
|
||||
});
|
||||
let drawn = entity.revision();
|
||||
kept.keep(pos, KEY, drawn, DEFAULT_BOUNDS, &[vertex(0.5)], 0);
|
||||
kept.keep(
|
||||
pos,
|
||||
KEY,
|
||||
drawn,
|
||||
DEFAULT_BOUNDS,
|
||||
&[vertex(0.5)],
|
||||
Vec::new(),
|
||||
0,
|
||||
);
|
||||
let entry = kept.get(pos, KEY, entity.revision(), 1).expect("kept");
|
||||
assert_eq!(entry.vertices.len(), 1);
|
||||
assert_eq!(entry.vertices[0].pos, [0.5, 0.0, 0.0]);
|
||||
@@ -731,6 +973,7 @@ mod tests {
|
||||
entity.revision(),
|
||||
DEFAULT_BOUNDS,
|
||||
&[vertex(0.25)],
|
||||
Vec::new(),
|
||||
2,
|
||||
);
|
||||
assert!(kept.get(pos, KEY, drawn, 2).is_none());
|
||||
@@ -748,8 +991,8 @@ mod tests {
|
||||
let mut kept = KeptGeometry::default();
|
||||
let (seen, gone) = ([0, 0, 0], [5, 0, 0]);
|
||||
let m = machine();
|
||||
kept.keep(seen, KEY, m, DEFAULT_BOUNDS, &[vertex(0.0)], 0);
|
||||
kept.keep(gone, KEY, m, DEFAULT_BOUNDS, &[vertex(0.0)], 0);
|
||||
kept.keep(seen, KEY, m, DEFAULT_BOUNDS, &[vertex(0.0)], Vec::new(), 0);
|
||||
kept.keep(gone, KEY, m, DEFAULT_BOUNDS, &[vertex(0.0)], Vec::new(), 0);
|
||||
kept.get(gone, KEY, m, 50);
|
||||
// A sweep at 100 keeps both: each was drawn in the last 100 ticks.
|
||||
kept.sweep(100);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
//! textures (resource packs included) in its own pass: with `WorldDrawn::block_sprites`, the arrays themselves.
|
||||
use super::Game;
|
||||
use horizon_client::block_sprites::{BlockSprites, Lookup, SpriteRef};
|
||||
use horizon_core::geometry::Direction;
|
||||
use horizon_world::registry::BlockStateId;
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Game<'_> {
|
||||
@@ -24,6 +26,30 @@ impl Game<'_> {
|
||||
.map(|texture| lookup.texture(texture))
|
||||
.collect()
|
||||
}
|
||||
/// For a client mod drawing a block entity: the texture block state `state`'s model shows on its `side`
|
||||
/// (`minecraft:block/oak_planks`), by the name `native_draw_cube` and `native_draw_quad` take, and that face's
|
||||
/// tint index (-1 for none): a copycat block drawing a box of its material's textures with `native_draw_cube`,
|
||||
/// kept with `BlockEntityDraw::keep`. The face culled against that side comes first, else the first facing it;
|
||||
/// None when the model has no face that way or isn't baked. API @API:block_face_sprite@.
|
||||
pub(super) fn native_block_face_sprite(
|
||||
&self,
|
||||
state: BlockStateId,
|
||||
side: Direction,
|
||||
) -> Option<(String, i32)> {
|
||||
let wanted = horizon_assets::model::Direction::ALL[side as usize];
|
||||
let models = self
|
||||
.pack
|
||||
.render
|
||||
.model_catalog
|
||||
.collect_seeded(state, 42)
|
||||
.ok()?;
|
||||
let faces = || models.iter().flat_map(|(_, model)| &model.faces);
|
||||
let face = faces()
|
||||
.find(|f| f.quad.cull == Some(wanted))
|
||||
.or_else(|| faces().find(|f| f.quad.facing == wanted))?;
|
||||
let name = self.pack.layer_names.get(&face.layer)?.clone();
|
||||
Some((name, face.quad.tint_index))
|
||||
}
|
||||
/// For a client mod: changes each time the resources load (a resource pack turned on or off can move every
|
||||
/// sprite): ask `native_block_sprites` again then. API @API:native_block_sprites@.
|
||||
pub(super) fn native_block_sprites_version(&self) -> u64 {
|
||||
|
||||
@@ -2718,6 +2718,20 @@ stock client shows as a chest (`generic_9x1` to `generic_9x6`); nothing is insta
|
||||
The menu hooks run with no player's state swapped in; `native_player_stacks` and `native_exchange_items` refuse a
|
||||
player whose state is (inside `native_perform_command`): call them from a menu hook or `native_server_tick`.
|
||||
|
||||
### Text, turning parts and copycats in block entities
|
||||
|
||||
- `native_draw_text(draw, text, at, color, glowing, outline)` draws a `Text` under a block entity's pose, as a sign
|
||||
draws its lines. One unit is one font pixel and y points down, so scale the pose by `[1/64, -1/64, 1/64]` as a sign
|
||||
does. Each span keeps its own colour, and glowing text is full bright. It returns the width, for centring.
|
||||
- `BlockEntityDraw::spin(axis, pivot, degrees_per_tick, phase)` makes what is drawn next turn by itself, and `still`
|
||||
ends it. The game works out the angle from the game time each frame. With `keep`, a shaft turning at a steady speed
|
||||
is drawn again with no call of `native_block_entity_submit` until its block entity changes. This stands in for
|
||||
Flywheel's rotating instances, on the CPU.
|
||||
- `native_block_face_sprite(state, side)` names the texture a block state shows on a side, as `native_draw_cube`
|
||||
takes it. With it, a copycat block draws its material's textures on its own shape and keeps the result.
|
||||
- Connected textures (casings, framed glass) need no code. A content mod ships OptiFine CTM rules under
|
||||
`assets/<id>/optifine/ctm/`, and the mesher reads them as it reads a resource pack's.
|
||||
|
||||
### Render hook points
|
||||
|
||||
`crates/horizon-client-render/src/gfx/native_render.rs`: the renderer's own hook points, free functions rather than
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "block_face_sprite", "target": "horizon_client::game::native_block_sprites::Game<'_>::native_block_face_sprite"}
|
||||
],
|
||||
"rust_summary": "the texture a block state shows on a side, by the name the block entity drawing helpers take (`native_block_face_sprite`), for copycat blocks.",
|
||||
"doc_summary": "`native_block_face_sprite(state, side)` names the texture a block state's model shows on a side and its tint index, as `native_draw_cube` takes it: a copycat block draws its material's textures on its own shape. Bundles need \"hooks\": @API:block_face_sprite@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_block_sprites.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{"name": "draw_text", "target": "horizon_client::game::native_block_entity_draw::Game<'_>::native_draw_text"}
|
||||
],
|
||||
"rust_summary": "text in the world from `native_block_entity_submit` (`native_draw_text`), as a sign draws its lines.",
|
||||
"doc_summary": "`native_draw_text(draw, text, at, color, glowing, outline)` draws a `Text` under a block entity's pose as a sign draws its lines (`Font.drawInBatch`): one unit a font pixel, each span in its colour, glowing text full bright, an optional outline; it returns the width. Bundles need \"hooks\": @API:draw_text@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_block_entity_draw.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"rust_summary": "turning parts of a block entity's kept geometry (`BlockEntityDraw::spin`, `still`): the game turns them each frame from the game time.",
|
||||
"doc_summary": "`BlockEntityDraw::spin(axis, pivot, degrees_per_tick, phase)` makes what is drawn next turn by itself and `still` ends it; kept with `keep`, a turning shaft is drawn every frame with no call of `native_block_entity_submit` until its block entity changes. Bundles need \"hooks\": @API:spinning_geometry@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_block_entity_draw.rs"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user