package d2isometricimg

import (
	
	
	
)

// A texture's sampled linear RGB modulates diffuse light. Specular light and
// emission are added afterwards; multiplying by the shaded white material
// would incorrectly tint those contributions with the source texture.
func svgTextureLighting( rasterCamera,  Triangle) (,  [3]float64) {
	 := rasterMaterial(.Material)
	 = [3]float64{.base.X, .base.Y, .base.Z}
	if .unlit {
		return , 
	}
	 := rasterLightingNormal(svgTriangleNormal(, 0), .direction)
	 := .32 + .12*rasterClamp(.Y*.5+.5)
	 := Vec{X: , Y: , Z: }
	 := Vec{}
	for ,  := range rasterLightDirections {
		 := math.Max(0, rasterDot(, )) * rasterLightStrengths[]
		 = rasterAdd(, rasterMul(rasterLightColors[], ))
		 := rasterUnit(rasterAdd(, .direction))
		 := math.Max(2, 2/(.roughness*.roughness)-2)
		 := math.Pow(math.Max(0, rasterDot(, )), ) *  * (.025 + .metalness*.10)
		 = rasterAdd(, rasterMul(rasterLightColors[], ))
	}
	 := 1 - .metalness*.08
	[0], [1], [2] = [0]*.X*, [1]*.Y*, [2]*.Z*
	 = [3]float64{.X + .emission.X, .Y + .emission.Y, .Z + .emission.Z}
	return , 
}

func svgTriangleNormal( Triangle,  int) Vec {
	 := .V[].Normal
	if nlen() < 1e-12 {
		 = nunit(ncross(nsub(.V[1].Position, .V[0].Position), nsub(.V[2].Position, .V[0].Position)))
	}
	return 
}

type svgLightingVertex struct {
	point    svgPoint
	position Vec
	normal   Vec
}

type svgGradientStop struct {
	offset float64
	color  Vec
}

type svgLightingGradient struct {
	start, end svgPoint
	stops      []svgGradientStop
	values     [3]float64
	valid      bool
}

type svgLightingPatch struct {
	vertices [3]svgLightingVertex
	gradient svgLightingGradient
}

func svgLightingColor( *Raster,  rasterPaint,  svgLightingVertex) Vec {
	, ,  := .shade(, .position, .normal)
	return nv(, , )
}

func svgLightingMix(,  svgLightingVertex,  float64) svgLightingVertex {
	return svgLightingVertex{
		point:    svgPoint{.point.x + *(.point.x-.point.x), .point.y + *(.point.y-.point.y), .point.z + *(.point.z-.point.z)},
		position: nlerp(.position, .position, ),
		normal:   nlerp(.normal, .normal, ),
	}
}

func svgColorError(,  Vec) float64 {
	return math.Max(math.Abs(.X-.X), math.Max(math.Abs(.Y-.Y), math.Abs(.Z-.Z)))
}

func svgGradientColor( []svgGradientStop,  float64) Vec {
	if  <= [0].offset {
		return [0].color
	}
	for  := 1;  < len(); ++ {
		if  <= [].offset {
			,  := [-1], []
			return nlerp(.color, .color, (-.offset)/(.offset-.offset))
		}
	}
	return [len()-1].color
}

// Cylinder and queue facets interpolate between two normals, even though each
// quad is triangulated. That variation has one affine coordinate. Sampling the
// actual shader along it preserves curved highlights and all RGB channels,
// instead of approximating a nonlinear highlight by two luminance endpoints.
func svgFitLightingGradient( *Raster,  rasterPaint,  [3]svgLightingVertex) (svgLightingGradient, float64) {
	, ,  := 0, 0, 0.0
	for  := 0;  < 3; ++ {
		for  :=  + 1;  < 3; ++ {
			 := nsub([].normal, [].normal)
			if  := ndot(, );  >  {
				, ,  = , , 
			}
		}
	}
	 := svgLightingGradient{}
	if  < 1e-20 {
		.stops = []svgGradientStop{{0, svgLightingColor(, , [0])}}
		return , 0
	}
	 := nsub([].normal, [].normal)
	for  := range  {
		.values[] = ndot(nsub([].normal, [].normal), ) / 
	}
	, ,  := [0].point, [1].point, [2].point
	 := (.x-.x)*(.y-.y) - (.x-.x)*(.y-.y)
	if math.Abs() < 1e-12 {
		.stops = []svgGradientStop{{0, svgLightingColor(, , [0])}}
		return , 0
	}
	 := .values
	 := (([1]-[0])*(.y-.y) - ([2]-[0])*(.y-.y)) / 
	 := ((.x-.x)*([2]-[0]) - (.x-.x)*([1]-[0])) / 
	 := * + *
	if  < 1e-24 {
		.stops = []svgGradientStop{{0, svgLightingColor(, , [0])}}
		return , 0
	}
	.start = [].point
	.end = svgPoint{x: .start.x + /, y: .start.y + /}
	.valid = true
	,  := svgLightingColor(, , []), svgLightingColor(, , [])
	.stops = append(.stops, svgGradientStop{0, })
	var  func(float64, float64, Vec, Vec, int)
	 = func(,  float64, ,  Vec,  int) {
		 := ( + ) / 2
		 := svgLightingColor(, , svgLightingMix([], [], ))
		if  < 6 && svgColorError(, nlerp(, , .5)) > .6/255 {
			(, , , , +1)
			(, , , , +1)
		} else {
			.stops = append(.stops, svgGradientStop{, })
		}
	}
	(0, 1, , , 0)
	 := 0.0
	for  := range  {
		 = math.Max(, svgColorError(svgLightingColor(, , []), svgGradientColor(.stops, [])))
		 := ( + 1) % 3
		 := svgLightingMix([], [], .5)
		 = math.Max(, svgColorError(svgLightingColor(, , ), svgGradientColor(.stops, ([]+[])/2)))
	}
	 := svgLightingMix(svgLightingMix([0], [1], .5), [2], 1.0/3)
	 = math.Max(, svgColorError(svgLightingColor(, , ), svgGradientColor(.stops, ([0]+[1]+[2])/3)))
	return , 
}

func svgLightingPatches( *Raster,  rasterPaint,  [3]svgLightingVertex) []svgLightingPatch {
	var  []svgLightingPatch
	var  func([3]svgLightingVertex, int)
	 = func( [3]svgLightingVertex,  int) {
		,  := svgFitLightingGradient(, , )
		// An occasional spherical facet needs a second spatial dimension. Keep
		// this bounded at 64 patches; normal native curve facets fit much earlier.
		if  <= 1.5/255 ||  == 3 {
			 = append(, svgLightingPatch{, })
			return
		}
		, ,  := svgLightingMix([0], [1], .5), svgLightingMix([1], [2], .5), svgLightingMix([2], [0], .5)
		([3]svgLightingVertex{[0], , }, +1)
		([3]svgLightingVertex{, [1], }, +1)
		([3]svgLightingVertex{, , [2]}, +1)
		([3]svgLightingVertex{, , }, +1)
	}
	(, 0)
	return 
}

func writeSVGLightingGradient( *nativeSVGWriter,  svgLightingGradient,  string) string {
	if !.valid {
		 := .stops[0].color
		return svgColor(.X, .Y, .Z)
	}
	.write(`<linearGradient id="%s" gradientUnits="userSpaceOnUse" color-interpolation="sRGB" x1="%s" y1="%s" x2="%s" y2="%s">`, , svgNumber(.start.x), svgNumber(.start.y), svgNumber(.end.x), svgNumber(.end.y))
	for ,  := range .stops {
		.write(`<stop offset="%s" stop-color="%s"/>`, svgNumber(.offset), svgColor(.color.X, .color.Y, .color.Z))
	}
	.write(`</linearGradient>`)
	return "url(#" +  + ")"
}

func writeSVGFaceGradient( *nativeSVGWriter,  *svgPaintBatch,  int,  ...rasterCamera) string {
	var  [3]svgLightingVertex
	for ,  := range .triangle.V {
		[] = svgLightingVertex{.points[], .Position, svgTriangleNormal(.triangle, )}
	}
	 := &Raster{camera: nativeCameraAxes()}
	if len() != 0 {
		.camera = [0]
	}
	// Lighting uses the fixed native view direction. Translation and image
	// fitting affect projection but never the orthographic lighting direction.
	 := svgLightingPatches(, rasterMaterial(.triangle.Material), )
	.write(`<defs>`)
	 := make([]string, len())
	for ,  := range  {
		[] = writeSVGLightingGradient(, .gradient, fmt.Sprintf("shade-%d-%d", , ))
	}
	if len() == 1 {
		.write(`</defs>`)
		return [0]
	}
	 := svgPolygonBox(.points[:])
	// The opaque base prevents antialiasing gaps between the small shading
	// patches. Material/source opacity is applied once to the enclosing face.
	,  := svgFitLightingGradient(, rasterMaterial(.triangle.Material), )
	 := writeSVGLightingGradient(, , fmt.Sprintf("shade-%d-base", ))
	.write(`<pattern id="shade-%d" patternUnits="userSpaceOnUse" x="%s" y="%s" width="%s" height="%s" viewBox="%s %s %s %s" preserveAspectRatio="none">`, , svgNumber(.minX), svgNumber(.minY), svgNumber(.maxX-.minX), svgNumber(.maxY-.minY), svgNumber(.minX), svgNumber(.minY), svgNumber(.maxX-.minX), svgNumber(.maxY-.minY))
	.write(`<path d="%s" fill="%s"/>`, svgPolygonPath([][]svgPoint{.points[:]}), )
	for ,  := range  {
		 := []svgPoint{.vertices[0].point, .vertices[1].point, .vertices[2].point}
		.write(`<path d="%s" fill="%s"/>`, svgPolygonPath([][]svgPoint{}), [])
	}
	.write(`</pattern></defs>`)
	return fmt.Sprintf("url(#shade-%d)", )
}

func svgCasterOpacity( Triangle) uint8 {
	if !.CastShadow || .Material != nil && .Material.Color.A == 0 {
		return 0
	}
	 := 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))
	}
	return 
}

func svgGroundCaster( Triangle, ,  float64,  rasterCamera,  Vec) []svgPoint {
	var  []Vec
	for ,  := range .V {
		,  := .Position, .V[(+1)%3].Position
		if .Y >=  {
			 = append(, )
		}
		if (.Y < ) != (.Y < ) {
			 = append(, nlerp(, , (-.Y)/(.Y-.Y)))
		}
	}
	var  []svgPoint
	for ,  := range  {
		 := nsub(, nmul(, (.Y-(+rasterShadowNormalOffset))/.Y))
		.Y = 
		 := .project()
		 = append(, svgProjectedPoint(.x, .y, ndot(, )))
	}
	return 
}

func writeSVGGroundShadow( *nativeSVGWriter,  *nativeScene) {
	if len(.triangles) == 0 {
		return
	}
	,  := rasterShadowGround(.triangles), rasterShadowDirection()
	 := rasterGroundTriangles(.triangles, , .camera.direction)
	 := rasterFit(, , 2048, 2048, 1.08)
	 := math.Max(.012, 2/.scale)
	 :=  + rasterShadowNormalOffset + *.Y
	var  []svgVisibilityFace
	var  []int
	var  []uint8
	for ,  := range  {
		if %512 == 0 {
			if .err = .ctx.Err(); .err != nil {
				return
			}
		}
		 := svgCasterOpacity()
		if  == 0 {
			continue
		}
		 := svgGroundCaster(, , , .camera, )
		if svgValidFragment() == nil {
			continue
		}
		 := .ShadowFillAlpha == nil && .Material != nil && .Material.Texture != nil
		 = append(, svgVisibilityFace{points: , opaque: !, order: -})
		,  = append(, ), append(, )
	}
	if len() == 0 {
		return
	}
	,  := svgVisibleFaces(.ctx, )
	if  != nil {
		.err = fmt.Errorf("native SVG ground shadow: %w", )
		return
	}
	// Native PCF has a three-sample footprint in both light-camera axes. A
	// single vector blur approximates its softness without triangle seams or
	// shadow bitmaps; its radius follows output density and light-camera fit.
	 := nsub(.right, nmul(, .right.Y/.Y))
	 := nsub(.up, nmul(, .up.Y/.Y))
	 := .camera.scale / .scale * math.Sqrt(2.0/3)
	 := math.Hypot(ndot(, .camera.right), ndot(, .camera.right)) * 
	 := math.Hypot(ndot(, .camera.up), ndot(, .camera.up)) * 
	.write(`<defs><filter id="ground-softness" filterUnits="userSpaceOnUse" x="0" y="0" width="%d" height="%d" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="%s %s"/></filter></defs><g filter="url(#ground-softness)">`, .width, .height, svgNumber(), svgNumber())
	 := make(map[uint8][][]svgPoint)
	 := make(map[uint8][]int)
	for ,  := range  {
		if len() == 0 {
			continue
		}
		 := [[]]
		if .ShadowFillAlpha == nil && .Material != nil && .Material.Texture != nil {
			[[]] = append([[]], )
		} else {
			[[]] = append([[]], ...)
		}
	}
	for  := 1;  <= 255; ++ {
		,  := [uint8()], [uint8()]
		if len() == 0 && len() == 0 {
			continue
		}
		// Assemble one silhouette before applying its shadow strength. An
		// opaque printed cap overlaps its sidewalls in light space; fading
		// them separately produces a dark ring around a pale hollow center.
		// Retained vector alpha still cuts genuine holes in image artwork.
		.write(`<g opacity="%s">`, svgNumber(.11*float64()/255))
		if len() != 0 {
			.write(`<path d="%s" fill="#243754"/>`, svgPolygonPath())
		}
		sort.SliceStable(, func(,  int) bool {
			 := func( int) float64 {
				 := 0.0
				for ,  := range [].points {
					 += .z
				}
				return  / float64(len([].points))
			}
			return ([]) < ([])
		})
		for ,  := range  {
			 := [[]]
			if .Material.Vector == nil {
				.err = fmt.Errorf("native SVG shadow surface %d has no retained vector source", [])
				return
			}
			 := .surfaceDefinition(.Material.Vector)
			if .err != nil {
				return
			}
			var  [3]svgPoint
			for ,  := range .V {
				 := nsub(.Position, nmul(, (.Position.Y-(+rasterShadowNormalOffset))/.Y))
				.Y = 
				 := .camera.project()
				[] = svgPoint{.x, .y, .z}
			}
			,  := svgTextureAffine(, )
			if  != nil {
				.err = 
				return
			}
			.write(`<defs><clipPath id="shadow-clip-%d"><path d="%s"/></clipPath><filter id="shadow-ink-%d" color-interpolation-filters="sRGB"><feColorMatrix values="0 0 0 0 0.14117647 0 0 0 0 0.21568627 0 0 0 0 0.32941176 0 0 0 1 0"/></filter></defs>`, , svgPolygonPath([]), )
			.write(`<g clip-path="url(#shadow-clip-%d)"><g transform="matrix(%s %s %s %s %s %s)" filter="url(#shadow-ink-%d)"><use xlink:href="#%s"/></g></g>`, , svgNumber([0]), svgNumber([1]), svgNumber([2]), svgNumber([3]), svgNumber([4]), svgNumber([5]), , )
		}
		.write(`</g>`)
	}
	.write(`</g>`)
}