1+ package dev .propulsionteam .propulsionsimulated .client .render .plume ;
2+
3+ import com .mojang .blaze3d .vertex .PoseStack ;
4+ import com .mojang .blaze3d .vertex .VertexConsumer ;
5+ import com .mojang .math .Axis ;
6+ import net .minecraft .client .renderer .MultiBufferSource ;
7+ import net .minecraft .client .renderer .ShaderInstance ;
8+ import net .minecraft .core .Direction ;
9+ import net .minecraft .util .Mth ;
10+ import net .minecraft .world .phys .Vec3 ;
11+ import org .joml .Matrix4f ;
12+ import org .joml .Quaternionf ;
13+
14+ public final class PlumeRenderer {
15+ private PlumeRenderer () {
16+ }
17+
18+ public static void render (PoseStack ms , MultiBufferSource buffer , PlumeRenderParams params , float time ) {
19+ Vec3 direction = new Vec3 (
20+ params .direction ().getStepX (),
21+ params .direction ().getStepY (),
22+ params .direction ().getStepZ ()
23+ );
24+ render (ms , buffer , params , time , direction );
25+ }
26+
27+ public static void render (PoseStack ms , MultiBufferSource buffer , PlumeRenderParams params , float time , Vec3 direction ) {
28+ if (params .power () <= 0.004f ) return ;
29+ if (params .alpha () <= 0.004f ) return ;
30+ if (params .length () <= 0.004f ) return ;
31+ if (params .radius () <= 0.004f ) return ;
32+ if (direction .lengthSqr () <= 1.0e-8d ) return ;
33+
34+ ShaderInstance shader = PlumeShaders .plume ();
35+ if (shader != null ) {
36+ set (shader , "Time" , time );
37+ set (shader , "Power" , params .power ());
38+ }
39+
40+ Vec3 dir = direction .normalize ();
41+
42+ ms .pushPose ();
43+ rotateLocalDownTo (ms , dir );
44+
45+ VertexConsumer vc = buffer .getBuffer (PlumeRenderType .plume ());
46+
47+ drawPlumeMesh (ms .last ().pose (), vc , params , params .length (), params .radius (), 32 , 18 , params .alpha (), time , 0.0f );
48+ drawPlumeMesh (ms .last ().pose (), vc , params , params .length () * 0.86f , params .radius () * 0.54f , 28 , 14 , params .alpha () * 0.82f , time , 0.35f );
49+ drawPlumeMesh (ms .last ().pose (), vc , params , params .length () * 0.62f , params .radius () * 0.22f , 24 , 10 , params .alpha () * 0.74f , time , 0.7f );
50+
51+ ms .popPose ();
52+ }
53+
54+ private static void rotateLocalDownTo (PoseStack ms , Direction direction ) {
55+ switch (direction ) {
56+ case DOWN -> {
57+ }
58+ case UP -> ms .mulPose (Axis .XP .rotationDegrees (180.0f ));
59+ case NORTH -> ms .mulPose (Axis .XP .rotationDegrees (90.0f ));
60+ case SOUTH -> ms .mulPose (Axis .XP .rotationDegrees (-90.0f ));
61+ case WEST -> ms .mulPose (Axis .ZP .rotationDegrees (-90.0f ));
62+ case EAST -> ms .mulPose (Axis .ZP .rotationDegrees (90.0f ));
63+ }
64+ }
65+
66+ private static void rotateLocalDownTo (PoseStack ms , Vec3 direction ) {
67+ Quaternionf q = new Quaternionf ().rotationTo (
68+ 0.0f ,
69+ -1.0f ,
70+ 0.0f ,
71+ (float ) direction .x ,
72+ (float ) direction .y ,
73+ (float ) direction .z
74+ );
75+ ms .mulPose (q );
76+ }
77+
78+ private static void drawPlumeMesh (Matrix4f matrix , VertexConsumer vc , PlumeRenderParams params , float length , float radius , int segments , int rings , float alpha , float time , float phase ) {
79+ for (int j = 0 ; j < rings ; j ++) {
80+ float t0 = (float ) j / rings ;
81+ float t1 = (float ) (j + 1 ) / rings ;
82+
83+ float y0 = -length * t0 ;
84+ float y1 = -length * t1 ;
85+
86+ float r0 = radiusAt (params .shape (), radius , t0 , time , phase );
87+ float r1 = radiusAt (params .shape (), radius , t1 , time , phase );
88+
89+ int a0 = alphaAt (alpha , t0 );
90+ int a1 = alphaAt (alpha , t1 );
91+
92+ for (int i = 0 ; i < segments ; i ++) {
93+ float u0 = (float ) i / segments ;
94+ float u1 = (float ) (i + 1 ) / segments ;
95+
96+ float [] p00 = point (params .shape (), r0 , u0 );
97+ float [] p01 = point (params .shape (), r0 , u1 );
98+ float [] p10 = point (params .shape (), r1 , u0 );
99+ float [] p11 = point (params .shape (), r1 , u1 );
100+
101+ v (vc , matrix , p00 [0 ], y0 , p00 [1 ], u0 , t0 , a0 , params );
102+ v (vc , matrix , p10 [0 ], y1 , p10 [1 ], u0 , t1 , a1 , params );
103+ v (vc , matrix , p11 [0 ], y1 , p11 [1 ], u1 , t1 , a1 , params );
104+ v (vc , matrix , p01 [0 ], y0 , p01 [1 ], u1 , t0 , a0 , params );
105+ }
106+ }
107+ }
108+
109+ private static float [] point (PlumeShape shape , float radius , float u ) {
110+ if (shape == PlumeShape .SQUARE ) return squarePoint (radius , u );
111+ return roundPoint (radius , u );
112+ }
113+
114+ private static float [] roundPoint (float radius , float u ) {
115+ float angle = Mth .TWO_PI * u ;
116+ return new float [] {
117+ Mth .cos (angle ) * radius ,
118+ Mth .sin (angle ) * radius
119+ };
120+ }
121+
122+ private static float [] squarePoint (float radius , float u ) {
123+ float p = (u - Mth .floor (u )) * 4.0f ;
124+
125+ if (p < 1.0f ) {
126+ return new float [] {
127+ Mth .lerp (p , radius , -radius ),
128+ -radius
129+ };
130+ }
131+
132+ if (p < 2.0f ) {
133+ return new float [] {
134+ -radius ,
135+ Mth .lerp (p - 1.0f , -radius , radius )
136+ };
137+ }
138+
139+ if (p < 3.0f ) {
140+ return new float [] {
141+ Mth .lerp (p - 2.0f , -radius , radius ),
142+ radius
143+ };
144+ }
145+
146+ return new float [] {
147+ radius ,
148+ Mth .lerp (p - 3.0f , radius , -radius )
149+ };
150+ }
151+
152+ private static float radiusAt (PlumeShape shape , float baseRadius , float t , float time , float phase ) {
153+ if (shape == PlumeShape .ROUND ) {
154+ float ionNeedle = 1.0f - smoothstep (0.18f , 1.0f , t ) * 0.48f ;
155+ float ionPulse = 1.0f + 0.008f * Mth .sin (time * 48.0f + phase * 13.0f + t * 24.0f );
156+ return baseRadius * ionNeedle * ionPulse ;
157+ }
158+
159+ float nearNozzle = Mth .lerp (smoothstep (0.0f , 0.12f , t ), 0.58f , 1.0f );
160+ float expansion = 1.0f + 0.42f * smoothstep (0.10f , 0.55f , t );
161+ float compression = 1.0f - 0.18f * smoothstep (0.55f , 0.82f , t );
162+ float tip = 1.0f - smoothstep (0.78f , 1.0f , t ) * 0.82f ;
163+ float pulse = 1.0f + 0.012f * Mth .sin (time * 34.0f + phase * 11.0f + t * 18.0f );
164+ return baseRadius * nearNozzle * expansion * compression * tip * pulse ;
165+ }
166+
167+ private static int alphaAt (float alpha , float t ) {
168+ float fadeIn = smoothstep (0.0f , 0.045f , t );
169+ float fadeOut = 1.0f - smoothstep (0.80f , 1.0f , t );
170+ float body = 1.0f - 0.22f * smoothstep (0.45f , 0.88f , t );
171+ return Mth .clamp ((int ) (alpha * 255.0f * fadeIn * fadeOut * body ), 0 , 255 );
172+ }
173+
174+ private static float smoothstep (float edge0 , float edge1 , float x ) {
175+ float t = Mth .clamp ((x - edge0 ) / (edge1 - edge0 ), 0.0f , 1.0f );
176+ return t * t * (3.0f - 2.0f * t );
177+ }
178+
179+ private static void v (VertexConsumer vc , Matrix4f matrix , float x , float y , float z , float u , float v , int alpha , PlumeRenderParams params ) {
180+ vc .addVertex (matrix , x , y , z )
181+ .setUv (u , v )
182+ .setColor (
183+ Mth .clamp ((int ) (params .red () * 255.0f ), 0 , 255 ),
184+ Mth .clamp ((int ) (params .green () * 255.0f ), 0 , 255 ),
185+ Mth .clamp ((int ) (params .blue () * 255.0f ), 0 , 255 ),
186+ alpha
187+ );
188+ }
189+
190+ private static void set (ShaderInstance shader , String name , float value ) {
191+ var uniform = shader .getUniform (name );
192+ if (uniform != null ) uniform .set (value );
193+ }
194+
195+ }
0 commit comments