package d2isometricimg

import (
	
	
	
	
	
	
	

	
)

type Vec = d2isometric.Vec3

// Vertex UV coordinates use a top-left texture origin. A zero normal uses
// the triangle's geometric normal; supplied normals are smoothly interpolated.
type Vertex struct {
	Position, Normal Vec
	U, V             float64
}

type Material struct {
	Color                color.NRGBA
	Roughness, Metalness float64
	Emissive             color.NRGBA
	Texture              image.Image
	Vector               *nativeVectorSurface
	Unlit                bool
	// Multiply composites this surface into the existing sRGB color while
	// retaining source-over alpha. Group regions normally also use NoDepthWrite.
	Multiply bool
	// SVG contours paint over their own substrate without cutting a second
	// antialiased boundary into it. Other objects still see opaque linework.
	svgContour bool
	// Physical solid caps and unwrapped walls have a filled source background;
	// their texture's antialiased perimeter is not a geometric hole.
	svgSolidTexture bool
	gradient        string
}

type Triangle struct {
	V          [3]Vertex
	Material   *Material
	CastShadow bool
	// Nil uses the material opacity; animation may fade its shadow separately
	// from the visible surface. The pointed value is immutable for a frame.
	ShadowOpacity *float64
	// Filled physical caps use source fill alpha rather than the printed
	// texture's antialiased outline coverage. Nil preserves texture holes.
	// ShadowOpacity still controls the strength of this physical shadow.
	ShadowFillAlpha *uint8
	// Hierarchy roots have independent implicit ground receivers. Nil keeps
	// the legacy scene-wide receiver. The pointed value is immutable.
	ShadowGround *float64
	DepthBias    float64
	NoDepthWrite bool
	// OpacityGroup fades a complete native object after its own opaque
	// surfaces and printed cells have resolved their mutual occlusion.
	OpacityGroup *nativeOpacityGroup
	// PaintOwner keeps a native object's substrate and decals together when
	// they must be ordered around a separate partial-opacity object.
	PaintOwner *nativePaintOwner
	// SVG uses source-contour caps to resolve texture coverage geometrically.
	// These caps neither paint nor participate in the physical ink silhouette.
	svgCoverageOnly bool
}

const (
	rasterMaxTriangles     = 1_000_000
	rasterMaxSamples       = 16_000_000
	rasterMaxTexturePixels = 64_000_000
	rasterMaxWork          = 500_000_000
	rasterCoordinateLimit  = 1e9
	rasterAA               = 2
	rasterStripRows        = 64
)

type rasterCamera struct {
	right, up, direction                 Vec
	centerX, centerY, centerDepth, scale float64
	width, height                        int
}

type rasterShadow struct {
	camera  rasterCamera
	depth   []float32
	opacity []uint8
	bias    float64
}

// Raster owns the static color/depth buffers and a directional shadow map.
// It is immutable after construction. Independent Frame calls may run in
// parallel; callers should bound that concurrency to their memory budget.
type Raster struct {
	width, height, aa int
	camera            rasterCamera
	shadow            rasterShadow
	groundShadow      rasterShadow
	pixels            *image.RGBA
	depth             []float32
	output            *image.RGBA
	static            *rasterDrawPlan // large images replay only touched strips
	background        color.NRGBA
	backgroundTexture *image.RGBA
	ground            float64
	groundBounds      image.Rectangle // finite receiver footprint of actual shadows
}

type rasterWork struct {
	ctx         context.Context
	remaining   int64
	groupPixels []uint8
	groupDepth  []float32
}

func ( *rasterWork) ( int64) error {
	if  := .ctx.Err();  != nil {
		return 
	}
	if  < 0 ||  > .remaining {
		return fmt.Errorf("native isometric raster work exceeds %d sample operations", rasterMaxWork)
	}
	.remaining -= 
	return nil
}

func rasterContext( context.Context) context.Context {
	if  == nil {
		return context.Background()
	}
	return 
}

// Native stills use a more open orthographic view: top faces are less
// foreshortened and their reading axis sits closer to horizontal.
// Gentle skew: 15 degrees of azimuth, retaining the 49.178-degree elevation.
// The fixed unit vector keeps all projection consumers on the same camera.
func nativeViewDirection() Vec {
	return Vec{X: 0.16919264451761212, Y: 0.7567450038061342, Z: 0.6314355456066684}
}

// NewRaster fits the occupied vertices with a fixed orthographic camera.
// It uses 2x supersampling at every supported size. Large surfaces render in
// bounded strips rather than dropping coverage quality or allocating all samples.
// Background is final opaque sRGB, unaffected by material lighting.
func ( context.Context, ,  int,  []Triangle,  color.NRGBA) (*Raster, error) {
	 := false
	for ,  := range  {
		 =  || .OpacityGroup != nil
	}
	if  > 0 &&  > 0 &&  <= 4096 &&  <= 4096 && int64()*int64() <= 12_000_000 && ( || int64()*int64()*rasterAA*rasterAA > rasterMaxSamples) {
		// The public API accepts caller-owned inputs. Deferred strip replay must
		// keep a snapshot even if that caller later changes its mesh or texture.
		var  error
		,  = rasterSnapshot(rasterContext(), )
		if  != nil {
			return nil, 
		}
	}
	return newRaster(, , , , , nil, nil)
}

func newRaster( context.Context, ,  int,  []Triangle,  color.NRGBA, ,  *rasterCamera,  ...*rasterCamera) (*Raster, error) {
	 = rasterContext()
	 := rasterWork{ctx: , remaining: rasterMaxWork}
	if  := .charge(0);  != nil {
		return nil, 
	}
	if  < 1 ||  < 1 ||  > 4096 ||  > 4096 || int64()*int64() > 12_000_000 {
		return nil, fmt.Errorf("native isometric raster dimensions exceed limits")
	}
	if  := rasterValidate(, );  != nil {
		return nil, 
	}
	 := &Raster{width: , height: , aa: rasterAA, background: , ground: rasterShadowGround()}
	.backgroundTexture, _ = .Value(nativeGradientBackgroundKey{}).(*image.RGBA)
	.camera = rasterFit(, nativeViewDirection(), *.aa, *.aa, 1.08)
	if  != nil {
		if .width < 1 || .height < 1 {
			return nil, fmt.Errorf("native raster camera requires positive dimensions")
		}
		.camera = cameraAtResolution(*, *.aa, *.aa)
	}
	var  error
	if  == nil {
		.shadow,  = rasterBuildShadow(&, )
	} else {
		.shadow,  = rasterBuildShadow(&, , *)
	}
	if  != nil {
		return nil, 
	}
	.groundShadow = .shadow
	 := rasterGroundTriangles(, .ground, .camera.direction)
	if len() > 0 && &[0] != &[0] {
		if  := .charge(int64(len()));  != nil {
			return nil, 
		}
		if len() > 0 && [0] != nil {
			.groundShadow,  = rasterBuildShadow(&, , *[0])
		} else {
			.groundShadow,  = rasterBuildShadow(&, )
		}
		if  != nil {
			return nil, 
		}
	}
	if len(.groundShadow.depth) > 0 {
		var  projectedExtent
		.preparedGroundShadows(, nil, 0, .ground, .groundShadow.camera)
		if .valid {
			 := float64(.camera.width)/2 + (.minX-.camera.centerX)*.camera.scale
			 := float64(.camera.width)/2 + (.maxX-.camera.centerX)*.camera.scale
			 := float64(.camera.height)/2 - (.maxY-.camera.centerY)*.camera.scale
			 := float64(.camera.height)/2 - (.minY-.camera.centerY)*.camera.scale
			.groundBounds = image.Rect(int(max(0, min(float64(.camera.width), math.Floor()))), int(max(0, min(float64(.camera.height), math.Floor()))), int(max(0, min(float64(.camera.width), math.Ceil()))), int(max(0, min(float64(.camera.height), math.Ceil()))))
		}
	}
	.output = image.NewRGBA(image.Rect(0, 0, , ))
	 := .rasterPrepare()
	if int64(.camera.width)*int64(.camera.height) > rasterMaxSamples || len(.groups) > 0 {
		.static = &
		if  := .renderStrips(&, .output, nil);  != nil {
			return nil, 
		}
		return , .Err()
	}
	.pixels = image.NewRGBA(image.Rect(0, 0, .camera.width, .camera.height))
	.depth = make([]float32, .camera.width*.camera.height)
	for  := range .depth {
		.depth[] = float32(math.Inf(-1))
	}
	if  = .paintGround(&, .pixels, );  != nil {
		return nil, 
	}
	if  = .drawPrepared(&, .pixels, .depth, , nil);  != nil {
		return nil, 
	}
	if  = .charge(int64() * int64() * int64(.aa*.aa));  != nil {
		return nil, 
	}
	for  := 0;  < ; ++ {
		if %32 == 0 {
			if  = .Err();  != nil {
				return nil, 
			}
		}
		for  := 0;  < ; ++ {
			.downsamplePixel(.output, .pixels, , )
		}
	}
	return , .Err()
}

// Frame reuses the static image and shadow map. Small images retain sample
// depth; large images replay the same static samples only in affected strips.
// Unchanged pixels remain identical across frames. The result is caller-owned.
func ( *Raster) ( context.Context,  []Triangle) (*image.RGBA, error) {
	 = rasterContext()
	if  := .Err();  != nil {
		return nil, 
	}
	if  == nil || .output == nil {
		return nil, fmt.Errorf("native raster is uninitialized")
	}
	if  := rasterValidate(, );  != nil {
		return nil, 
	}
	 := image.NewRGBA(.output.Rect)
	copy(.Pix, .output.Pix)
	if len() == 0 {
		return , .Err()
	}
	if .static != nil {
		 := rasterWork{ctx: , remaining: rasterMaxWork}
		 := .rasterPrepare()
		if  := .renderStrips(&, , &);  != nil {
			return nil, 
		}
		return , .Err()
	}
	 := image.NewRGBA(.pixels.Rect)
	copy(.Pix, .pixels.Pix)
	 := append([]float32(nil), .depth...)
	 := make([]bool, .width*.height)
	 := rasterWork{ctx: , remaining: rasterMaxWork}
	if  := .draw(&, , , , );  != nil {
		return nil, 
	}
	for  := 0;  < .height; ++ {
		if %32 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		for  := 0;  < .width; ++ {
			if [*.width+] {
				.downsamplePixel(, , , )
			}
		}
	}
	return , .Err()
}

func rasterSnapshot( context.Context,  []Triangle) ([]Triangle, error) {
	if  := rasterValidate(, );  != nil {
		return nil, 
	}
	 := append([]Triangle(nil), ...)
	 := make(map[*Material]*Material)
	 := make(map[image.Image]image.Image)
	 := make(map[*nativeOpacityGroup]*nativeOpacityGroup)
	 := make(map[*nativePaintOwner]*nativePaintOwner)
	for  := range  {
		if  := .Err();  != nil {
			return nil, 
		}
		 := &[]
		if .PaintOwner != nil {
			,  := [.PaintOwner]
			if ! {
				 := *.PaintOwner
				 = &
				[.PaintOwner] = 
			}
			.PaintOwner = 
		}
		if .OpacityGroup != nil {
			,  := [.OpacityGroup]
			if ! {
				 := *.OpacityGroup
				 = &
				[.OpacityGroup] = 
			}
			.OpacityGroup = 
		}
		if .ShadowOpacity != nil {
			 := *.ShadowOpacity
			.ShadowOpacity = &
		}
		if .ShadowFillAlpha != nil {
			 := *.ShadowFillAlpha
			.ShadowFillAlpha = &
		}
		if .ShadowGround != nil {
			 := *.ShadowGround
			.ShadowGround = &
		}
		if .Material == nil {
			continue
		}
		,  := [.Material]
		if ! {
			 := *.Material
			 = &
			[.Material] = 
			if .Texture != nil {
				,  := [.Texture]
				if ! {
					 := .Texture.Bounds()
					var  draw.Image
					switch .Texture.(type) {
					case *image.RGBA:
						 = image.NewRGBA()
					case *image.NRGBA:
						 = image.NewNRGBA()
					case *image.Alpha:
						 = image.NewAlpha()
					default:
						return nil, fmt.Errorf("unsupported native isometric texture type %T", .Texture)
					}
					for  := .Min.Y;  < .Max.Y; ++ {
						if %64 == 0 {
							if  := .Err();  != nil {
								return nil, 
							}
						}
						draw.Draw(, image.Rect(.Min.X, , .Max.X, +1), .Texture, image.Pt(.Min.X, ), draw.Src)
					}
					 = 
					[.Texture] = 
				}
				.Texture = 
			}
		}
		.Material = 
	}
	return , .Err()
}

// Large images retain final pixels and an immutable native draw plan, not a
// full supersampled color/depth surface. Replay uses global sample coordinates,
// so strip boundaries cannot change coverage, shadow lookup or triangle order.
func ( *Raster) ( *rasterWork,  *image.RGBA,  *rasterDrawPlan) error {
	if  := .charge(0);  != nil {
		return 
	}
	 := min(rasterStripRows, .height)
	 :=  * .aa
	 := make([]uint8, .camera.width**4)
	 := make([]float32, .camera.width*)
	 := make([]bool, (.height+-1)/)
	 := *.static
	// A cached translucent object cannot be painted underneath later moving
	// geometry. Replay their bounded draw plan together in the touched strips.
	 :=  != nil && (len(.groups) > 0 || len(.groups) > 0)
	if  {
		if len(.triangles) > rasterMaxTriangles-len(.triangles) {
			return fmt.Errorf("combined native isometric triangles exceed %d", rasterMaxTriangles)
		}
		if  := .charge(int64(len(.triangles) + len(.triangles)));  != nil {
			return 
		}
		 := append(append([]Triangle(nil), .triangles...), .triangles...)
		 = .rasterPrepare()
	}
	if  != nil {
		for ,  := range .triangles {
			if  := .charge(1);  != nil {
				return 
			}
			var  [3]rasterPoint
			for ,  := range .V {
				[] = .camera.project(.Position)
			}
			, , ,  := rasterBounds(, .camera.width, .camera.height)
			if  >=  ||  >=  {
				continue
			}
			for  :=  / ;  <= (-1)/; ++ {
				[] = true
			}
		}
	}
	for  := 0;  < .height;  +=  {
		if  := .charge(0);  != nil {
			return 
		}
		if  != nil && ![/] {
			continue
		}
		 := min(.height, +)
		 := image.Rect(0, *.aa, .camera.width, *.aa)
		 := .Dx() * .Dy()
		 := &image.RGBA{Pix: [:*4], Stride: .Dx() * 4, Rect: }
		 := [:]
		for  := range  {
			[] = float32(math.Inf(-1))
		}
		if  := .paintGround(, , .background);  != nil {
			return 
		}
		if  := .drawPrepared(, , , , nil);  != nil {
			return 
		}
		if  != nil && ! {
			if  := .drawPrepared(, , , *, nil);  != nil {
				return 
			}
		}
		if  := .charge(int64());  != nil {
			return 
		}
		for  := ;  < ; ++ {
			for  := 0;  < .width; ++ {
				.downsamplePixel(, , , )
			}
		}
	}
	return .ctx.Err()
}

func rasterValidate( context.Context,  []Triangle) error {
	if len() > rasterMaxTriangles {
		return fmt.Errorf("native isometric triangles exceed %d", rasterMaxTriangles)
	}
	 := make(map[image.Image]bool)
	var  int64
	for ,  := range  {
		if %1024 == 0 {
			if  := .Err();  != nil {
				return 
			}
		}
		if !rasterFinite(.DepthBias) || math.Abs(.DepthBias) > rasterCoordinateLimit {
			return fmt.Errorf("triangle %d has invalid depth bias", )
		}
		if .ShadowOpacity != nil && (!rasterFinite(*.ShadowOpacity) || *.ShadowOpacity < 0 || *.ShadowOpacity > 1) {
			return fmt.Errorf("triangle %d has invalid shadow opacity", )
		}
		if .ShadowGround != nil && (!rasterFinite(*.ShadowGround) || math.Abs(*.ShadowGround) > rasterCoordinateLimit) {
			return fmt.Errorf("triangle %d has invalid shadow ground", )
		}
		if .OpacityGroup != nil && (!rasterFinite(.OpacityGroup.Opacity) || .OpacityGroup.Opacity < 0 || .OpacityGroup.Opacity > 1) {
			return fmt.Errorf("triangle %d has invalid object opacity", )
		}
		for ,  := range .V {
			for ,  := range []float64{.Position.X, .Position.Y, .Position.Z, .Normal.X, .Normal.Y, .Normal.Z, .U, .V} {
				if !rasterFinite() || math.Abs() > rasterCoordinateLimit {
					return fmt.Errorf("triangle %d has invalid vertex", )
				}
			}
		}
		 := .Material
		if  == nil {
			continue
		}
		if !rasterFinite(.Roughness) || !rasterFinite(.Metalness) {
			return fmt.Errorf("triangle %d has nonfinite material", )
		}
		if .Texture != nil {
			// Restrict textures to owned standard raster types. Their comparable
			// pointers also make shared-atlas admission independent of triangle count.
			switch texture := .Texture.(type) {
			case *image.RGBA:
				if  == nil {
					return fmt.Errorf("nil native isometric texture")
				}
				if  := rasterTextureStorage(.Rect, .Stride, len(.Pix), 4);  != nil {
					return 
				}
			case *image.NRGBA:
				if  == nil {
					return fmt.Errorf("nil native isometric texture")
				}
				if  := rasterTextureStorage(.Rect, .Stride, len(.Pix), 4);  != nil {
					return 
				}
			case *image.Alpha:
				if  == nil {
					return fmt.Errorf("nil native isometric texture")
				}
				if  := rasterTextureStorage(.Rect, .Stride, len(.Pix), 1);  != nil {
					return 
				}
			default:
				return fmt.Errorf("unsupported native isometric texture type %T", .Texture)
			}
			if ![.Texture] {
				 := .Texture.Bounds()
				if .Dx() < 1 || .Dy() < 1 || .Dx() > 16384 || .Dy() > 16384 {
					return fmt.Errorf("invalid native isometric texture dimensions")
				}
				 += int64(.Dx()) * int64(.Dy())
				if  > rasterMaxTexturePixels {
					return fmt.Errorf("native isometric texture pixels exceed limit")
				}
				[.Texture] = true
			}
		}
	}
	return nil
}

func rasterTextureStorage( image.Rectangle, , ,  int) error {
	if .Min.X < -1e9 || .Min.Y < -1e9 || .Max.X > 1e9 || .Max.Y > 1e9 || .Max.X <= .Min.X || .Max.Y <= .Min.Y || .Dx() > 16384 || .Dy() > 16384 ||  < .Dx()* {
		return fmt.Errorf("invalid native isometric texture storage")
	}
	// Divide the available storage before comparing the stride: a caller-owned
	// image can supply a stride whose multiplication by the row count overflows.
	 := .Dx() * 
	if  <  || .Dy() > 1 &&  > (-)/(.Dy()-1) {
		return fmt.Errorf("short native isometric texture storage")
	}
	return nil
}

// Round vector products explicitly, as in the mesh helpers, so camera and
// geometry calculations agree on CPUs with and without fused arithmetic.
func rasterFinite( float64) bool { return !math.IsNaN() && !math.IsInf(, 0) }
func rasterAdd(,  Vec) Vec      { return Vec{X: .X + .X, Y: .Y + .Y, Z: .Z + .Z} }
func rasterSub(,  Vec) Vec      { return Vec{X: .X - .X, Y: .Y - .Y, Z: .Z - .Z} }
func rasterMul( Vec,  float64) Vec {
	return Vec{X: float64(.X * ), Y: float64(.Y * ), Z: float64(.Z * )}
}
func rasterDot(,  Vec) float64 { return float64(.X*.X) + float64(.Y*.Y) + float64(.Z*.Z) }
func rasterCross(,  Vec) Vec {
	return Vec{X: float64(.Y*.Z) - float64(.Z*.Y), Y: float64(.Z*.X) - float64(.X*.Z), Z: float64(.X*.Y) - float64(.Y*.X)}
}
func rasterUnit( Vec) Vec {
	 := math.Sqrt(rasterDot(, ))
	if  < 1e-15 {
		return Vec{Y: 1}
	}
	return rasterMul(, 1/)
}
func rasterClamp( float64) float64 { return math.Max(0, math.Min(1, )) }

func rasterFit( []Triangle,  Vec, ,  int,  float64) rasterCamera {
	 = rasterUnit()
	 := rasterUnit(rasterCross(Vec{Y: 1}, ))
	 := rasterUnit(rasterCross(, ))
	, , , , ,  := math.Inf(1), math.Inf(1), math.Inf(1), math.Inf(-1), math.Inf(-1), math.Inf(-1)
	for ,  := range  {
		for ,  := range .V {
			 := .Position
			, ,  := rasterDot(, ), rasterDot(, ), rasterDot(, )
			 = math.Min(, )
			 = math.Max(, )
			 = math.Min(, )
			 = math.Max(, )
			 = math.Min(, )
			 = math.Max(, )
		}
	}
	if math.IsInf(, 1) {
		, , , , ,  = -1, -1, -1, 1, 1, 1
	}
	 := math.Max(.02, math.Max(-, (-)*float64()/float64())) * 
	return rasterCamera{right: , up: , direction: , centerX: ( + ) / 2, centerY: ( + ) / 2, centerDepth: ( + ) / 2, scale: float64() / , width: , height: }
}

type rasterPoint struct{ x, y, z float64 }

func ( rasterCamera) ( Vec) rasterPoint {
	return rasterPoint{x: float64((rasterDot(, .right)-.centerX)*.scale) + float64(.width)/2, y: float64(.height)/2 - float64((rasterDot(, .up)-.centerY)*.scale), z: rasterDot(, .direction) - .centerDepth}
}
func ( rasterCamera) (, ,  float64) Vec {
	return rasterAdd(rasterAdd(rasterMul(.right, (-float64(.width)/2)/.scale+.centerX), rasterMul(.up, (float64(.height)/2-)/.scale+.centerY)), rasterMul(.direction, +.centerDepth))
}
func rasterOrient(, ,  rasterPoint) float64 {
	// Evaluate each shared edge from one canonical endpoint. Reversing an
	// edge must produce the exact opposite value, including at subpixel
	// boundaries where two independently rounded expressions can both be
	// negative. The top-left rule then assigns every sample to one face.
	 := 1.
	if .x > .x || .x == .x && .y > .y {
		,  = , 
		 = -1
	}
	return  * ((.x-.x)*(.y-.y) - (.y-.y)*(.x-.x))
}
func rasterTopLeft(,  rasterPoint) bool { return .y < .y || (.y == .y && .x > .x) }

func rasterBounds( [3]rasterPoint, ,  int) (int, int, int, int) {
	,  := math.Min([0].x, math.Min([1].x, [2].x)), math.Max([0].x, math.Max([1].x, [2].x))
	,  := math.Min([0].y, math.Min([1].y, [2].y)), math.Max([0].y, math.Max([1].y, [2].y))
	// Clamp floats before integer conversion; even finite world coordinates
	// can project far outside the canvas after fitting tiny static geometry.
	return int(math.Max(0, math.Min(float64(), math.Floor()))), int(math.Max(0, math.Min(float64(), math.Floor()))), int(math.Max(0, math.Min(float64(), math.Ceil()))), int(math.Max(0, math.Min(float64(), math.Ceil())))
}

// Intersect the three oriented half-planes at the sample row. Long, thin
// diagonal tubes otherwise test almost their entire empty bounding rectangle.
// The one-pixel guard preserves the existing barycentric/top-left decisions
// at floating-point boundaries; this function only reduces candidate work.
func rasterRowSpan( [3]rasterPoint, , ,  int) (int, int) {
	,  := float64()+.5, float64()-.5
	 := float64() + .5
	for ,  := range  {
		 := [(+1)%3]
		,  := .x-.x, .y-.y
		if  == 0 {
			if *(-.y) < 0 {
				return , 
			}
			continue
		}
		 := .x + *((-.y)/)
		if  < 0 {
			 = math.Max(, )
		} else {
			 = math.Min(, )
		}
	}
	if  > +1e-9 {
		return , 
	}
	// Clamp before conversion, including triangles projected far off canvas.
	return int(math.Max(float64(), math.Min(float64(), math.Floor()-1))), int(math.Max(float64(), math.Min(float64(), math.Ceil()+1)))
}

const rasterShadowNormalOffset = .025

func rasterShadowDirection() Vec { return Vec{X: -1, Y: 1.8, Z: 1} }

func rasterShadowGround( []Triangle) float64 {
	 := 0.
	for ,  := range  {
		for ,  := range .V {
			 = min(, .Position.Y)
		}
	}
	// Keep the receiver close to the board substrate, so surface traces do
	// not appear suspended above a distant ground plane.
	return  + .08
}

func rasterBuildShadow( *rasterWork,  []Triangle,  ...rasterCamera) (rasterShadow, error) {
	 := false
	for ,  := range  {
		if .CastShadow && (.ShadowOpacity == nil || *.ShadowOpacity > 0) && (.Material == nil || .Material.Color.A > 0) {
			 = true
			break
		}
	}
	if ! {
		return rasterShadow{}, nil
	}
	 := rasterShadow{camera: rasterFit(, rasterShadowDirection(), 2048, 2048, 1.08)}
	if len() > 0 {
		.camera = [0]
	}
	.depth = make([]float32, 2048*2048)
	.opacity = make([]uint8, 2048*2048)
	for  := range .depth {
		.depth[] = float32(math.Inf(-1))
	}
	.bias = math.Max(.012, 2/.camera.scale)
	for ,  := range  {
		if %512 == 0 {
			if  := .charge(0);  != nil {
				return , 
			}
		}
		if !.CastShadow || (.ShadowOpacity != nil && *.ShadowOpacity <= 0) || (.Material != nil && .Material.Color.A == 0) {
			continue
		}
		var  [3]rasterPoint
		 := .V
		for ,  := range .V {
			[] = .camera.project(.Position)
		}
		 := rasterOrient([0], [1], [2])
		if math.Abs() < 1e-10 {
			continue
		}
		if  < 0 {
			[1], [2] = [2], [1]
			[1], [2] = [2], [1]
			 = -
		}
		, , ,  := rasterBounds(, 2048, 2048)
		if  := .charge(int64(-) * 3);  != nil {
			return , 
		}
		 := uint8(255)
		if .Material != nil {
			 = .Material.Color.A
		}
		if .ShadowFillAlpha != nil {
			 = *.ShadowFillAlpha
		}
		if .ShadowOpacity != nil {
			 = uint8(math.Round(float64() * *.ShadowOpacity))
		}
		if .OpacityGroup != nil {
			 = uint8(math.Round(float64() * .OpacityGroup.Opacity))
		}
		for  := ;  < ; ++ {
			if %32 == 0 {
				if  := .ctx.Err();  != nil {
					return , 
				}
			}
			,  := rasterRowSpan(, , , )
			if  := .charge(int64( - ));  != nil {
				return , 
			}
			for  := ;  < ; ++ {
				 := rasterPoint{x: float64() + .5, y: float64() + .5}
				, ,  := rasterOrient([1], [2], ), rasterOrient([2], [0], ), rasterOrient([0], [1], )
				if  < 0 ||  < 0 ||  < 0 {
					continue
				}
				 := float32((*[0].z + *[1].z + *[2].z) / )
				 := 
				if .ShadowFillAlpha == nil && .Material != nil && .Material.Texture != nil {
					 := (*[0].U + *[1].U + *[2].U) / 
					 := (*[0].V + *[1].V + *[2].V) / 
					, , ,  := rasterTexture(.Material.Texture, , )
					 = uint8(math.Round(float64() * ))
					if  == 0 {
						continue
					}
				}
				 := *2048 + 
				if  > .depth[] {
					.depth[] = 
					.opacity[] = 
				}
			}
		}
	}
	return , nil
}

func ( *rasterShadow) (,  Vec) float64 {
	if len(.depth) == 0 {
		return 0
	}
	 := .camera.project(rasterAdd(, rasterMul(, rasterShadowNormalOffset)))
	if .x < 1 || .y < 1 || .x >= float64(.camera.width-1) || .y >= float64(.camera.height-1) {
		return 0
	}
	,  := int(.x), int(.y)
	 := 0.0
	for  := -1;  <= 1; ++ {
		for  := -1;  <= 1; ++ {
			 := (+)*.camera.width +  + 
			if float64(.depth[]) > .z+.bias {
				 += float64(.opacity[]) / 255
			}
		}
	}
	return  / 9
}

func ( *Raster) ( *rasterWork,  *image.RGBA,  color.NRGBA) error {
	 := .ground
	 := int64(.Rect.Dx()) * int64(.Rect.Dy())
	 := .Rect.Intersect(.groundBounds)
	 :=  + int64(.Dx())*int64(.Dy())*9
	if  := .charge();  != nil {
		return 
	}
	for  := .Rect.Min.Y;  < .Rect.Max.Y; ++ {
		if %32 == 0 {
			if  := .ctx.Err();  != nil {
				return 
			}
		}
		for  := .Rect.Min.X;  < .Rect.Max.X; ++ {
			 := 0.
			if  >= .Min.X &&  < .Max.X &&  >= .Min.Y &&  < .Max.Y {
				 := .camera.world(float64()+.5, float64()+.5, 0)
				 = rasterAdd(, rasterMul(.camera.direction, (-.Y)/.camera.direction.Y))
				 = .groundShadow.occlusion(, Vec{Y: 1}) * .11
			}
			 := 
			if .backgroundTexture != nil {
				 = nativeGradientBackground(.backgroundTexture, (float64()+.5)/float64(.camera.width), (float64()+.5)/float64(.camera.height), )
			}
			 := .PixOffset(, )
			.Pix[] = uint8(math.Round(float64(.R)*(1-) + 36*))
			.Pix[+1] = uint8(math.Round(float64(.G)*(1-) + 55*))
			.Pix[+2] = uint8(math.Round(float64(.B)*(1-) + 84*))
			.Pix[+3] = 255
		}
	}
	return nil
}

type rasterPaint struct {
	base, emission              Vec
	alpha, roughness, metalness float64
	texture                     image.Image
	unlit                       bool
	multiply                    bool
}

func rasterMaterial( *Material) rasterPaint {
	if  == nil {
		 = &Material{Color: color.NRGBA{R: 240, G: 243, B: 249, A: 255}, Roughness: .35}
	}
	return rasterPaint{base: Vec{X: rasterLinear(float64(.Color.R) / 255), Y: rasterLinear(float64(.Color.G) / 255), Z: rasterLinear(float64(.Color.B) / 255)}, emission: Vec{X: rasterLinear(float64(.Emissive.R)/255) * float64(.Emissive.A) / 255, Y: rasterLinear(float64(.Emissive.G)/255) * float64(.Emissive.A) / 255, Z: rasterLinear(float64(.Emissive.B)/255) * float64(.Emissive.A) / 255}, alpha: float64(.Color.A) / 255, roughness: math.Max(.05, rasterClamp(.Roughness)), metalness: rasterClamp(.Metalness), texture: .Texture, unlit: .Unlit, multiply: .Multiply}
}
func rasterLinear( float64) float64 {
	if  <= .04045 {
		return  / 12.92
	}
	return math.Pow((+.055)/1.055, 2.4)
}
func rasterSRGB( float64) float64 {
	 = rasterClamp()
	if  <= .0031308 {
		return  * 12.92
	}
	return 1.055*math.Pow(, 1/2.4) - .055
}

type rasterDrawPlan struct {
	triangles []Triangle
	order     []int
	paints    map[*Material]rasterPaint
	groups    map[int]rasterOpacityPlan
}

func ( *Raster) ( *rasterWork,  *image.RGBA,  []float32,  []Triangle,  []bool) error {
	return .drawPrepared(, , , .rasterPrepare(), )
}

func ( *Raster) ( []Triangle) rasterDrawPlan {
	for ,  := range  {
		if .OpacityGroup != nil {
			return .rasterPrepareOpacityGroups()
		}
	}
	 := make([]int, len())
	 := make(map[*Material]rasterPaint)
	for ,  := range  {
		[] = 
		if ,  := [.Material]; ! {
			[.Material] = rasterMaterial(.Material)
		}
	}
	 := func( Triangle) bool {
		return .NoDepthWrite || [.Material].multiply || [.Material].alpha < .999 || [.Material].texture != nil
	}
	sort.SliceStable(, func(,  int) bool {
		,  := [[]], [[]]
		// Printed icons and labels do not write depth. Draw their substrates
		// first, including textured faces whose transparent margins prevented
		// classification as opaque. Centroid sorting alone can otherwise paint
		// half of a large face over a small decal on that very same surface.
		if .NoDepthWrite != .NoDepthWrite {
			return !.NoDepthWrite
		}
		,  := (), ()
		if  !=  {
			return !
		}
		if ! {
			return false
		}
		,  := 0.0, 0.0
		for  := 0;  < 3; ++ {
			 += rasterDot(.V[].Position, .camera.direction)
			 += rasterDot(.V[].Position, .camera.direction)
		}
		return  < 
	})
	return rasterDrawPlan{triangles: , order: , paints: }
}

func ( *Raster) ( *rasterWork,  *image.RGBA,  []float32,  rasterDrawPlan,  []bool) error {
	,  := .triangles, .paints
	for ,  := range .order {
		if  := .charge(1);  != nil {
			return 
		}
		if ,  := .groups[];  {
			if  := .drawOpacityGroup(, , , , );  != nil {
				return 
			}
			continue
		}
		 := []
		 := [.Material]
		if .alpha == 0 {
			continue
		}
		// Physical meshes use outward normals. Rendering their rear faces a
		// second time would compound authored translucency. Opaque geometry
		// remains two-sided so winding cannot silently remove a solid face.
		if .alpha < .999 || .multiply {
			 := rasterAdd(rasterAdd(.V[0].Normal, .V[1].Normal), .V[2].Normal)
			if rasterDot(, ) > 1e-20 && rasterDot(, .camera.direction) <= 0 {
				continue
			}
		}
		var  [3]rasterPoint
		for ,  := range .V {
			[] = .camera.project(.Position)
		}
		 := rasterOrient([0], [1], [2])
		if math.Abs() < 1e-10 {
			continue
		}
		if  < 0 {
			[1], [2] = [2], [1]
			.V[1], .V[2] = .V[2], .V[1]
			 = -
		}
		 := rasterUnit(rasterCross(rasterSub(.V[1].Position, .V[0].Position), rasterSub(.V[2].Position, .V[0].Position)))
		for  := range .V {
			if rasterDot(.V[].Normal, .V[].Normal) < 1e-20 {
				.V[].Normal = 
			}
		}
		, , ,  := rasterBounds(, .camera.width, .camera.height)
		, , ,  = max(, .Rect.Min.X), max(, .Rect.Min.Y), min(, .Rect.Max.X), min(, .Rect.Max.Y)
		if  >=  ||  >=  {
			continue
		}
		if  := .charge(int64(-) * 3);  != nil {
			return 
		}
		, ,  := rasterTopLeft([1], [2]), rasterTopLeft([2], [0]), rasterTopLeft([0], [1])
		for  := ;  < ; ++ {
			if %16 == 0 {
				if  := .ctx.Err();  != nil {
					return 
				}
			}
			,  := rasterRowSpan(, , , )
			if  := .charge(int64( - ));  != nil {
				return 
			}
			for  := ;  < ; ++ {
				 := rasterPoint{x: float64() + .5, y: float64() + .5}
				, ,  := rasterOrient([1], [2], ), rasterOrient([2], [0], ), rasterOrient([0], [1], )
				if  < 0 ||  < 0 ||  < 0 || ( == 0 && !) || ( == 0 && !) || ( == 0 && !) {
					continue
				}
				 /= 
				 /= 
				 /= 
				 := float32(*[0].z + *[1].z + *[2].z + .DepthBias)
				 := (-.Rect.Min.Y)*.Rect.Dx() +  - .Rect.Min.X
				if  < [] {
					continue
				}
				 := .alpha
				, ,  := 0.0, 0.0, 0.0
				if .texture != nil {
					,  := *.V[0].U+*.V[1].U+*.V[2].U, *.V[0].V+*.V[1].V+*.V[2].V
					var  float64
					, , ,  = rasterTexture(.texture, , )
					 *= 
					if  < .015 {
						continue
					}
					// Unlit label atlases are premultiplied during interpolation,
					// then unpremultiplied once before final over compositing.
					if !.unlit {
						 := rasterWeighted(.V[0].Position, .V[1].Position, .V[2].Position, , , )
						 := rasterWeighted(.V[0].Normal, .V[1].Normal, .V[2].Normal, , , )
						 := 
						.base = Vec{X: rasterLinear() * .base.X, Y: rasterLinear() * .base.Y, Z: rasterLinear() * .base.Z}
						, ,  = .shade(, , )
					}
				} else {
					 := rasterWeighted(.V[0].Position, .V[1].Position, .V[2].Position, , , )
					 := rasterWeighted(.V[0].Normal, .V[1].Normal, .V[2].Normal, , , )
					, ,  = .shade(, , )
				}
				 := .PixOffset(, )
				if .multiply {
					rasterMultiplyOver(.Pix[:+4], , , , )
				} else {
					.Pix[] = uint8(math.Round(rasterClamp()**255 + float64(.Pix[])*(1-)))
					.Pix[+1] = uint8(math.Round(rasterClamp()**255 + float64(.Pix[+1])*(1-)))
					.Pix[+2] = uint8(math.Round(rasterClamp()**255 + float64(.Pix[+2])*(1-)))
				}
				if !.NoDepthWrite &&  >= .999 {
					[] = 
				}
				if  != nil {
					[(/.aa)*.width+/.aa] = true
				}
			}
		}
	}
	return nil
}

// rasterMultiplyOver blends a straight sRGB source into a premultiplied RGBA
// destination. The uncovered backdrop fraction must receive the source color:
// multiplying by zero there would incorrectly turn transparent pixels black.
func rasterMultiplyOver( []uint8, , , ,  float64) {
	 = rasterClamp()
	 := float64([3]) / 255
	for ,  := range [3]float64{, , } {
		 := rasterClamp()
		 := float64([]) / 255
		 := **(1-) + *(1-+*)
		[] = uint8(math.Round(rasterClamp() * 255))
	}
	[3] = uint8(math.Round(( + *(1-)) * 255))
}

func rasterWeighted(, ,  Vec, , ,  float64) Vec {
	return Vec{X: .X* + .X* + .X*, Y: .Y* + .Y* + .Y*, Z: .Z* + .Z* + .Z*}
}

func rasterTexture( image.Image, ,  float64) (float64, float64, float64, float64) {
	 := .Bounds()
	 := rasterClamp()*float64(.Dx()) - .5
	 := rasterClamp()*float64(.Dy()) - .5
	,  := int(math.Floor()), int(math.Floor())
	,  := -float64(), -float64()
	, , ,  := 0.0, 0.0, 0.0, 0.0
	for  := 0;  <= 1; ++ {
		for  := 0;  <= 1; ++ {
			 := 1.0
			if  == 0 {
				 *= 1 - 
			} else {
				 *= 
			}
			if  == 0 {
				 *= 1 - 
			} else {
				 *= 
			}
			,  := max(0, min(.Dx()-1, +))+.Min.X, max(0, min(.Dy()-1, +))+.Min.Y
			, , ,  := .At(, ).RGBA()
			 += float64() / 65535 * 
			 += float64() / 65535 * 
			 += float64() / 65535 * 
			 += float64() / 65535 * 
		}
	}
	if  <= 1e-10 {
		return 0, 0, 0, 0
	}
	return  / ,  / ,  / , 
}

func ( *Raster) (,  *image.RGBA, ,  int) {
	, ,  := 0, 0, 0
	for  := 0;  < .aa; ++ {
		for  := 0;  < .aa; ++ {
			 := .PixOffset(*.aa+, *.aa+)
			 += int(.Pix[])
			 += int(.Pix[+1])
			 += int(.Pix[+2])
		}
	}
	 := .aa * .aa
	 := .PixOffset(, )
	.Pix[] = uint8(( + /2) / )
	.Pix[+1] = uint8(( + /2) / )
	.Pix[+2] = uint8(( + /2) / )
	.Pix[+3] = 255
}