package d2isometricimg

import (
	
	
	
	
	
	
	

	

	
	
	
	
	
	
	
	libcolor 
)

// The native renderer turns the compiled scene into meshes with layout-sized
// footprints and physical extrusion.
type nativeScene struct {
	raster            *Raster
	camera            rasterCamera // logical output coordinates, also used by SVG
	packets           []packetRoute
	triangles         []Triangle
	panels            []nativePanel
	width, height     int
	background        color.NRGBA
	backgroundPaint   d2scene.Paint
	backgroundTexture *image.RGBA
	animatedNodes     []nativeAnimatedNode
	pixelScale        float64
	links             []nativeLink
}
type meshBuilder struct {
	ctx               context.Context
	triangles         []Triangle
	err               error
	text              *textPainter
	rich              *richLabelPainter
	icons             *surfaceIconPainter
	scale             float64
	panels            []nativePanel
	animatedNodes     []nativeAnimatedNode
	facePixels        int64
	faceMaxPixels     int64
	arrowBackground   string
	arrowOwners       map[string]*nativeArrowOwner
	arrowMarkers      map[nativeArrowMarkerKey]nativeArrowMarker
	arrowWork         int64
	arrowCachePoints  int
	routeCasingFloor  float64
	hierarchySupports map[string]float64
	nodeSupportDrop   float64
	fonts             *d2scenebuild.FontFallbackOptions
	outputDensity     float64
	options           nativeSceneOptions
	links             []nativeLink
	linkBytes         int
	gradientTextures  map[string]*image.RGBA
}
type labelSurface struct {
	center              Vec
	width, depth, angle float64
	align               string
}

const maxNativeTriangles = 1_000_000

// Explicit product conversions prevent CPU-dependent FMA contraction after
// inlining. Last-bit drift can change clipped ink topology and SVG batching.
func nv(, ,  float64) Vec    { return Vec{X: , Y: , Z: } }
func nadd(,  Vec) Vec         { return nv(.X+.X, .Y+.Y, .Z+.Z) }
func nsub(,  Vec) Vec         { return nv(.X-.X, .Y-.Y, .Z-.Z) }
func nmul( Vec,  float64) Vec { return nv(float64(.X*), float64(.Y*), float64(.Z*)) }
func ndot(,  Vec) float64     { return float64(.X*.X) + float64(.Y*.Y) + float64(.Z*.Z) }
func ncross(,  Vec) Vec {
	return nv(float64(.Y*.Z)-float64(.Z*.Y), float64(.Z*.X)-float64(.X*.Z), float64(.X*.Y)-float64(.Y*.X))
}
func nlen( Vec) float64 { return math.Sqrt(ndot(, )) }
func nunit( Vec) Vec {
	if  := nlen();  > 1e-12 {
		return nmul(, 1/)
	}
	return nv(0, 1, 0)
}
func nlerp(,  Vec,  float64) Vec { return nadd(, nmul(nsub(, ), )) }
func nativeToken( string) bool {
	if  == "" {
		return true
	}
	for ,  := range []string{"N", "B", "AA", "AB"} {
		if strings.HasPrefix(, ) {
			 := strings.TrimPrefix(, )
			if  != "" && strings.Trim(, "0123456789") == "" {
				return true
			}
		}
	}
	return false
}
func nativePaint(,  string) color.NRGBA {
	if libcolor.IsGradient() {
		if ,  := nativeGradientSummary();  {
			return 
		}
	}
	if nativeToken() {
		 = 
	}
	,  := csscolorparser.Parse()
	if  != nil {
		, _ = csscolorparser.Parse()
	}
	, , ,  := .RGBA255()
	return color.NRGBA{R: , G: , B: , A: }
}
func nativeMaterial( string, , ,  float64) *Material {
	 := nativePaint(, "#edf1f7")
	.A = uint8(math.Round(float64(.A) * max(0, min(1, ))))
	 := &Material{Color: , Roughness: , Metalness: }
	if libcolor.IsGradient() {
		.Color = color.NRGBA{255, 255, 255, uint8(math.Round(255 * max(0, min(1, ))))}
		.gradient = 
	}
	return 
}
func tintDark( color.NRGBA,  float64) color.NRGBA {
	.R = uint8(float64(.R) * )
	.G = uint8(float64(.G) * )
	.B = uint8(float64(.B) * )
	return 
}

func ( *meshBuilder) (, ,  Vertex,  *Material,  bool) {
	if .err != nil ||  == nil || .Color.A == 0 {
		return
	}
	if len(.triangles) >= maxNativeTriangles {
		.err = fmt.Errorf("isometric geometry exceeds %d triangles", maxNativeTriangles)
		return
	}
	if len(.triangles)%1024 == 0 {
		if  := .ctx.Err();  != nil {
			.err = 
			return
		}
	}
	.triangles = append(.triangles, Triangle{V: [3]Vertex{, , }, Material: , CastShadow: })
}
func ( *meshBuilder) (, ,  Vec,  *Material,  bool) {
	 := nunit(ncross(nsub(, ), nsub(, )))
	.triangle(Vertex{Position: , Normal: }, Vertex{Position: , Normal: }, Vertex{Position: , Normal: }, , )
}

// Project each face of a cube onto a rounded cuboid. Shared boundary positions
// and analytic normals keep bevel seams continuous without changing dimensions.
func ( *meshBuilder) (,  Vec,  *Material,  float64) {
	if .err != nil || .X <= 0 || .Y <= 0 || .Z <= 0 {
		return
	}
	 := nmul(, .5)
	 = min(, min(.X, min(.Y, .Z))*.24)
	 := []float64{.X, .Y, .Z}
	 := nv(max(0, .X-), max(0, .Y-), max(0, .Z-))
	for  := 0;  < 3; ++ {
		 := ( + 1) % 3
		 := ( + 2) % 3
		 := func( float64) []float64 {
			if  < 1e-6 {
				return []float64{-, }
			}
			return []float64{-, - + ,  - , }
		}
		,  := ([]), ([])
		for ,  := range []float64{-1, 1} {
			 := func(,  float64) Vertex {
				 := [3]float64{}
				[] = [] * 
				[] = 
				[] = 
				 := nv([0], [1], [2])
				 := nv(max(-.X, min(.X, .X)), max(-.Y, min(.Y, .Y)), max(-.Z, min(.Z, .Z)))
				 := nunit(nsub(, ))
				if  < 1e-6 {
					 = [3]float64{}
					[] = 
					 = nv([0], [1], [2])
				} else {
					 = nadd(, nmul(, ))
				}
				return Vertex{Position: nadd(, ), Normal: }
			}
			for  := 1;  < len(); ++ {
				for  := 1;  < len(); ++ {
					, , ,  := ([-1], [-1]), ([], [-1]), ([], []), ([-1], [])
					if  > 0 {
						.triangle(, , , , true)
						.triangle(, , , , true)
					} else {
						.triangle(, , , , true)
						.triangle(, , , , true)
					}
				}
			}
		}
	}
}

func ( *meshBuilder) (,  Vec,  *Material, ,  int) {
	for  := 0;  < ; ++ {
		for  := 0;  < ; ++ {
			 := func(,  int) Vertex {
				 := 2 * math.Pi * float64() / float64()
				 := math.Pi * float64() / float64()
				 := nv(math.Sin()*math.Cos(), math.Cos(), math.Sin()*math.Sin())
				 := nunit(nv(.X/max(.X, 1e-8), .Y/max(.Y, 1e-8), .Z/max(.Z, 1e-8)))
				return Vertex{Position: nadd(, nv(.X*.X, .Y*.Y, .Z*.Z)), Normal: }
			}
			, , ,  := (, ), (+1, ), (+1, +1), (, +1)
			.triangle(, , , , true)
			.triangle(, , , , true)
		}
	}
}

// Elliptic cylinders and frusta, optionally laid along X for queue barrels.
func ( *meshBuilder) ( Vec, , , ,  float64, ,  *Material,  int,  bool) {
	 := func( Vec) Vec {
		if  {
			return nadd(, nv(.Y, -.X, .Z))
		}
		return nadd(, )
	}
	 := func( Vec) Vec {
		if  {
			return nv(.Y, -.X, .Z)
		}
		return 
	}
	for  := 0;  < ; ++ {
		 := func( int,  bool) Vertex {
			 := 2 * math.Pi * float64() / float64()
			,  := 1., -/2
			if  {
				,  = , /2
			}
			 := nv(**math.Cos(), , **math.Sin())
			 := nunit(nv(math.Cos()/max(, 1e-8), (1-)/max(, 1e-8), math.Sin()/max(, 1e-8)))
			return Vertex{Position: (), Normal: ()}
		}
		, , ,  := (, false), (+1, false), (+1, true), (, true)
		.triangle(, , , , true)
		.triangle(, , , , true)
		for ,  := range []bool{false, true} {
			,  := (, ), (+1, )
			,  := -/2, nv(0, -1, 0)
			if  {
				,  = /2, nv(0, 1, 0)
			}
			.Normal = ()
			.Normal = .Normal
			 := Vertex{Position: (nv(0, , 0)), Normal: .Normal}
			if  {
				.triangle(, , , , true)
			} else {
				.triangle(, , , , true)
			}
		}
	}
}

func ( *meshBuilder) ( string,  labelSurface,  d2target.Text,  string,  float64,  string) {
	if .err != nil ||  == "" || .width <= 0 || .depth <= 0 ||  <= 0 {
		return
	}
	.Label = 
	 := .printStyle(, , , , )
	var  *image.RGBA
	var  error
	 := isRichLabel(d2target.Shape{Text: })
	if  {
		,  = .rich.texture(d2target.Shape{Text: }, )
	} else {
		, _,  = .text.texture(, )
	}
	if  != nil {
		.err = 
		return
	}
	.surfaceTexture(, , 1)
	if  {
		.addDocumentLinks(.rich.lastDocument, )
	}
}

func ( *meshBuilder) ( labelSurface,  d2target.Text,  string,  float64,  string) labelTextStyle {
	 := float64(.FontSize)
	if  <= 0 {
		 = 23.5
		if  == "board" {
			 = 24.5
		}
		if  == "edge" {
			 = 19
		}
	}
	var  *color.NRGBA
	if .LabelFill != "" && !nativeToken(.LabelFill) {
		 := nativePaint(.LabelFill, "transparent")
		 = &
		if .A > 0 && nativeToken(.Color) {
			 = readableSurfaceInk(.LabelFill, )
		}
	}
	 := labelTextStyle{Foreground: , Width: .width, Depth: .depth, FontSize: , PixelScale: .scale, FontFamily: .FontFamily, Bold: .Bold, Italic: .Italic, Underline: .Underline, Color: nativePaint(, "#253650"), Background: , Opacity: , Align: .align, MaxLines: 64}
	for ,  := range []struct {
		   string
		 *d2scene.Paint
	}{{, &.ForegroundPaint}, {.LabelFill, &.BackgroundPaint}} {
		if libcolor.IsGradient(.) {
			,  := scenepaint.Gradient(.)
			if  != nil {
				.err = 
			} else {
				*. = 
			}
		}
	}
	return 
}

func ( *meshBuilder) ( d2target.Shape,  labelSurface,  string,  float64,  string) {
	if .err != nil || .width <= 0 || .depth <= 0 ||  <= 0 {
		return
	}
	if !isRichLabel() {
		.label(.Label, , .Text, , , )
		return
	}
	,  := .rich.texture(, .printStyle(, .Text, , , ))
	if  != nil {
		.err = 
		return
	}
	.surfaceTexture(, , 1)
	.addDocumentLinks(.rich.lastDocument, )
}

func ( *meshBuilder) ( d2target.Shape,  labelSurface,  float64,  string,  ...float64) labelSurface {
	if .err != nil || .Icon == nil ||  <= 0 {
		return 
	}
	,  := surfaceIconLayout(, , .scale, )
	if .width <= 0 || .depth <= 0 {
		return 
	}
	 := float64(.IconBorderRadius)
	if len() > 0 {
		 = [0]
	}
	,  := .icons.texture(.Icon, .width, .depth, *.scale)
	if  != nil {
		.err = fmt.Errorf("isometric icon for %q: %w", .ID, )
		return 
	}
	.surfaceTexture(, , )
	return 
}

// Default ink follows the physical face, while an authored font color remains
// authoritative. This also keeps ordinary labels readable on dark components.
func readableSurfaceInk( string,  float64) string {
	 := nativePaint(, "#edf1f7")
	 := float64(.A) / 255 * 
	 := 0.
	for ,  := range []uint8{.R, .G, .B} {
		 := (float64()* + 247*(1-)) / 255
		if  <= .04045 {
			 /= 12.92
		} else {
			 = math.Pow((+.055)/1.055, 2.4)
		}
		 +=  * []float64{.2126, .7152, .0722}[]
	}
	if  < .24 {
		return "#f5f8fc"
	}
	return "#253650"
}

// Surface artwork shares the same physical plane and camera projection as its
// component. Alpha is composited without obscuring the material underneath.
func ( *meshBuilder) ( *image.RGBA,  labelSurface,  float64) {
	if .err != nil ||  == nil || .width <= 0 || .depth <= 0 ||  <= 0 {
		return
	}
	 := &Material{Color: color.NRGBA{255, 255, 255, uint8(math.Round(255 * min(1, )))}, Texture: , Unlit: true}
	.Vector = nativeVectorForTexture(.ctx, )
	 := nv(math.Cos(.angle), 0, math.Sin(.angle))
	 := nv(-math.Sin(.angle), 0, math.Cos(.angle))
	 := func(,  float64) Vertex {
		return Vertex{Position: nadd(.center, nadd(nmul(, (-.5)*.width), nmul(, (-.5)*.depth))), Normal: nv(0, 1, 0), U: , V: }
	}
	 := len(.triangles)
	.triangle((0, 0), (0, 1), (1, 1), , false)
	.triangle((0, 0), (1, 1), (1, 0), , false)
	for  := ;  < len(.triangles); ++ {
		.triangles[].NoDepthWrite = true
		.triangles[].DepthBias = .0005
	}
}

func newNativeScene( context.Context,  *d2isometric.Scene, ,  int,  ...*d2scenebuild.AssetOptions) (*nativeScene, error) {
	var  *d2scenebuild.AssetOptions
	if len() > 0 {
		 = [0]
	}
	return newNativeSceneWithFonts(, , , , , nil)
}

func newNativeSceneWithFonts( context.Context,  *d2isometric.Scene, ,  int,  *d2scenebuild.AssetOptions,  *d2scenebuild.FontFallbackOptions) (*nativeScene, error) {
	return newNativeSceneWithOptions(, , , , , , nativeSceneOptions{})
}

func newNativeSceneWithOptions( context.Context,  *d2isometric.Scene, ,  int,  *d2scenebuild.AssetOptions,  *d2scenebuild.FontFallbackOptions,  nativeSceneOptions) (*nativeScene, error) {
	 := len(.Boards) + len(.Nodes) + len(.Edges)*3 + 2
	,  := newTextPainter(, )
	if  != nil {
		return nil, 
	}
	.configureFontFamilies(.FontFamily, .MonoFontFamily)
	.configureFallbackFonts()
	.configureOutputDensity(.outputDensity)
	,  := 0, 0
	if isRichLabel(.Root) {
		++
	}
	if .Root.Icon != nil {
		++
	}
	for ,  := range .Nodes {
		if isRichLabel(.Metadata.Original) {
			++
		}
		if .Icon != "" {
			++
		}
	}
	for ,  := range .Edges {
		for ,  := range []*d2target.Text{&.Metadata.Original.Text, .SourceLabel, .TargetLabel} {
			if  != nil && isRichLabel(d2target.Shape{Text: *}) {
				++
			}
		}
		if .Icon != "" {
			++
		}
	}
	,  := newRichLabelPainter(, )
	if  != nil {
		return nil, 
	}
	.configureFontFamilies(.FontFamily, .MonoFontFamily)
	.configureTheme(.ThemeID, .ThemeOverrides)
	.configureFallbackFonts()
	.configureOutputDensity(.outputDensity)
	,  := newSurfaceIconPainter(, , )
	if  != nil {
		return nil, 
	}
	.configureOutputDensity(.outputDensity)
	 := &meshBuilder{ctx: , text: , rich: , icons: , scale: .PixelScale, fonts: , options: , outputDensity: .outputDensity}
	.faceMaxPixels = max(64, 24*1024*1024/int64(max(1, len(.Nodes)+len(.Boards))))
	 := d2themescatalog.Find(.ThemeID)
	.ApplyOverrides(.ThemeOverrides)
	.arrowBackground = d2themes.ResolveThemeColor(, d2target.BG_COLOR)
	 := newRouteCaptionPlacer()
	if .scale <= 0 {
		.scale = .01
	}
	 := []string{"#7898ba", "#9287b7", "#7da89c", "#b69b80"}
	 := []int{}
	 := map[int]bool{}
	for ,  := range .Boards {
		if ![.Level] {
			 = append(, .Level)
			[.Level] = true
		}
	}
	sort.Ints()
	 := map[string]*d2isometric.Node{}
	for  := range .Nodes {
		[.Nodes[].ID] = &.Nodes[]
	}
	 := map[string]string{}
	 := hierarchyPresentationBoards(.Boards, )
	 := "#f5f7fb"
	.routeCasingFloor = hierarchyCasingFloor(, )
	.hierarchySupports = hierarchySupportOffsets()
	 := hierarchyRenderNodes(.Nodes, .hierarchySupports)
	if !nativeToken(.Root.Fill) {
		 = .Background
	}
	 := map[string]d2isometric.Board{}
	var  []hierarchyShadowSpan
	for ,  := range  {
		[.ID] = 
		 := [sort.SearchInts(, .Level)%len()]
		[.ID] = hierarchyBoardTint([.SourceID], )
	}
	for ,  := range  {
		if  := .Err();  != nil {
			return nil, 
		}
		 := [.SourceID]
		 := 1.
		 := [sort.SearchInts(, .Level)%len()]
		if  != nil {
			 = .Opacity
		}
		 = hierarchyBoardTint(, )
		[.ID] = 
		if  <= 0 {
			continue
		}
		,  := max(.01, .Size.X), max(.01, .Size.Z)
		,  := len(.triangles), len(.animatedNodes)
		.hierarchyBoard(, , , )

		if .Label != "" ||  != nil && .Icon != "" {
			 := d2target.Text{}
			 := hierarchyHeaderInk(, , , , )
			if  != nil {
				 = .Metadata.Original.Text
				if .FontExplicit {
					 = .FontColor
				}
			}
			 := .HeaderDepth
			if  <= 0 {
				 = .62
			}
			,  := float64(.LabelWidth), float64(.LabelHeight)
			if  <= 0 {
				 = float64(len([]rune(.Label))) * 14
			}
			if  <= 0 {
				 = 30
			}
			,  := min(, *.scale), min(, *.scale)
			if  != nil && .Icon != "" {
				 = min(max(0, -.7), ++.12)
			}
			 := labelSurface{center: nadd(.Position, nv(-/2+.35+/2, .032, -/2+/2)), width: , depth: , align: "left"}
			if  != nil {
				.center.Y = hierarchySurfaceY() + .00006
				 = hierarchyBoardHeaderSurface(, , *, , .scale, )
			}
			.Avoid(.center, , )
			if  != nil {
				 = .shapeIcon(.Metadata.Original, , , "board")
			}
			if  != nil {
				 := .Metadata.Original
				if .FontExplicit && nativeToken(.LabelFill) && hierarchyHeaderInk(, , , , ) == readableSurfaceInk(, 1) {
					// Preserve authored light ink when a dark source panel becomes
					// a pale organizational wash. A small printed backing keeps
					// the title readable without restoring a full opaque plate.
					.LabelFill = 
					if nativePaint(, "transparent").A == 0 || readableSurfaceInk(, 1) == readableSurfaceInk(, 1) {
						.LabelFill = readableSurfaceInk(, 1)
					}
				}
				.shapeLabel(, , , , "board")
			} else {
				.label(.Label, , , , , "board")
			}
		}
		if  != nil {
			nativePhysicalShadows(.triangles[:], .Metadata.Original.Shadow)
			.addMeshLink(.Metadata.Original.Link, .Metadata.Original.Tooltip, .triangles[:])
			if .Metadata.Original.Animated && len(.triangles) >  {
				if len(.animatedNodes) ==  {
					.animatedNodes = append(.animatedNodes, nativeAnimatedNode{first: , last: len(.triangles)})
				} else {
					.animatedNodes[].last = len(.triangles)
				}
			}
		}
		 = append(, hierarchyShadowSpan{, len(.triangles), .ID})
		if  != nil {
			 := 
			.rememberArrowOwner(*, &, )
		}
	}
	for ,  := range .Nodes {
		if !.Container && .Opacity > 0 {
			if hierarchySpacer() {
				continue
			}
			 := len(.triangles)
			if .SequenceRole != "" {
				.sequenceNode(, [.BoardID])
			} else {
				.hierarchyNode(, [.BoardID])
			}
			.AvoidMesh(.triangles[:])
			.addMeshLink(.Metadata.Original.Link, .Metadata.Original.Tooltip, .triangles[:])
			 = append(, hierarchyShadowSpan{, len(.triangles), .BoardID})
			.rememberArrowOwner(, nil, )
		}
	}
	 := .edges(.Edges, , )
	if .err != nil {
		return nil, .err
	}
	if  := .rootDecorations(, );  != nil {
		return nil, 
	}
	hierarchyShadowReceivers(.triangles, , )
	return finishNativeScene(, , , , )
}

func ( *meshBuilder) ( d2isometric.Node,  string,  ...float64) {
	 = nativeClassicNode()
	 := len(.triangles)
	 := len(.links)
	if nativeMarkdownCard() {
		.markdownCard(, )
	} else if nativeStructuredNode() {
		.structuredNode()
	} else if nativeSolidNode() {
		.solidNode()
	} else {
		.canonicalNode(, )
	}
	 := 1.
	if len() > 0 {
		 = [0]
	}
	var  Vec
	if len(.triangles) >  {
		 = .triangles[len(.triangles)-1].V[0].Position
	}
	if .SequenceRole == "" && !nativeStructuredNode() && !nativeMarkdownCard() {
		.clearOutsideCaption(, , )
	}
	if len(.triangles) >  {
		 := nsub(.triangles[len(.triangles)-1].V[0].Position, )
		for  := ;  < len(.links); ++ {
			for  := range .links[].points {
				.links[].points[] = nadd(.links[].points[], )
			}
		}
	}
	if len() == 0 && !nativeStructuredNode() && !nativeMarkdownCard() {
		.classicInkEdges(, .triangles[:])
	}
	nativePhysicalShadows(.triangles[:], .Metadata.Original.Shadow)
	if .Metadata.Original.Animated && len(.triangles) >  {
		.animatedNodes = append(.animatedNodes, nativeAnimatedNode{first: , last: len(.triangles)})
	}
	.markPaintOwner()
}

// Physical contact shadows establish depth independently of D2's optional
// graphic drop-shadow style. An authored shadow strengthens that treatment;
// ordinary 3D components still cast a restrained shadow onto their surface.
func nativePhysicalShadows( []Triangle,  bool) {
	 := .72
	if  {
		 = 1
	}
	for  := range  {
		if [].CastShadow {
			[].ShadowOpacity = &
		}
	}
}

func ( *nativeScene) ( context.Context,  float64,  bool,  ...float64) (*image.RGBA, error) {
	if math.IsNaN() || math.IsInf(, 0) ||  < 0 {
		return nil, fmt.Errorf("isometric frame time must be finite and nonnegative")
	}
	var  []Triangle
	if  {
		 := &meshBuilder{ctx: }
		 := 
		if len() > 0 {
			 = [0]
		}
		if math.IsNaN() || math.IsInf(, 0) ||  < 0 {
			return nil, fmt.Errorf("isometric packet time must be finite and nonnegative")
		}
		 :=  / CycleSeconds
		 -= math.Floor()
		if  < 1e-12 || 1- < 1e-12 {
			 = 0
		}
		for ,  := range .packets {
			for  := 0;  < 2; ++ {
				 := math.Mod(+float64()/2, 1)
				if .reverse && (!.forward || %2 != 0) {
					 = 1 - 
				}
				 := pathPoint(.points, .lengths, )
				.sphere(, nv(nativeTrafficRadius, nativeTrafficRadius, nativeTrafficRadius), .material, 10, 8)
			}
		}
		if .err != nil {
			return nil, .err
		}
		 = .triangles
	}
	,  := .frameRaster(, , )
	if  != nil {
		return nil, 
	}
	return .Frame(, )
}