mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
bec13d523f
commit
b3c4bbc844
45 files changed
+1885
-1453
No files matched your search
+6
-223
@@ -22,7 +22,7 @@ import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
basethreed "github.com/xiaoqidun/ofdgo/internal/base/threed"
|
||||
codecpdf "github.com/xiaoqidun/ofdgo/internal/codec/pdf"
|
||||
"github.com/xiaoqidun/pdfgo"
|
||||
)
|
||||
|
||||
@@ -185,226 +185,9 @@ func (p *pdfImporter) threeDPainter(ctx context.Context, page *pdfgo.Page, sourc
|
||||
// 入参: ctx 取消上下文, model 解码后的U3D模型, source PDF三维参数, options 输出与资源限制
|
||||
// 返回: *image.RGBA 不透明图像, error 参数、能力、预算或取消错误
|
||||
func RenderPDFThreeD(ctx context.Context, model *U3DModel, source pdfgo.ThreeD, options PDFThreeDRenderOptions) (*image.RGBA, error) {
|
||||
if ctx == nil {
|
||||
return nil, fmt.Errorf("invalid PDF 3D render context")
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if source.Format != "U3D" {
|
||||
return nil, &pdfgo.UnsupportedError{Feature: "3D model format " + string(source.Format)}
|
||||
}
|
||||
if len(source.Measurements) != 0 {
|
||||
return nil, &pdfgo.UnsupportedError{Feature: "3D view measurement rendering"}
|
||||
}
|
||||
presentation := source.Presentation
|
||||
if presentation.Overlay != nil {
|
||||
return nil, &pdfgo.UnsupportedError{Feature: "3D view overlay"}
|
||||
}
|
||||
if source.Background.RGB == nil {
|
||||
return nil, &pdfgo.UnsupportedError{Feature: "3D background color space"}
|
||||
}
|
||||
box := source.ViewBox
|
||||
width, height := box.XMax-box.XMin, box.YMax-box.YMin
|
||||
if !finite(width) || !finite(height) || width <= 0 || height <= 0 {
|
||||
return nil, fmt.Errorf("invalid PDF 3D viewport")
|
||||
}
|
||||
projection := pdfgo.ThreeDProjection{Subtype: "P", Clipping: "ANF", FieldOfView: 90, PerspectiveBinding: "W"}
|
||||
if source.Projection != nil {
|
||||
projection = *source.Projection
|
||||
}
|
||||
render := U3DRenderOptions{Width: options.Width, Height: options.Height, MaxMemoryBytes: options.MaxMemoryBytes, MaxInstances: options.MaxInstances, Background: source.Background.RGB, ResolveTexture: options.ResolveTexture, Time: options.Time}
|
||||
if err := pdfThreeDRenderMode(source, &render); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pdfThreeDNodeStates(ctx, presentation.Nodes, &render); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pdfThreeDSections(ctx, presentation.CrossSections, &render); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := basethreed.NewRenderer(ctx, model, render, options.Nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
camera, view, err := pdfThreeDCamera(ctx, model, r, source.Camera)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
render.View = view
|
||||
camera.ScaleX, camera.ScaleY, camera.Near = 1, 1, 1
|
||||
render.Camera = &camera
|
||||
passes, err := r.SelectView(render)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.PrepareSections(render.Sections); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scale, err := projection.ScaleFactor(source.ViewBox, source.AnnotationBox)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
camera.Perspective = projection.Subtype == "P"
|
||||
camera.ScaleX = scale * float64(options.Width) / width
|
||||
camera.ScaleY = scale * float64(options.Height) / height
|
||||
switch projection.Clipping {
|
||||
case "XNF":
|
||||
camera.Near = 0
|
||||
if projection.Near != nil {
|
||||
camera.Near = *projection.Near
|
||||
}
|
||||
if !finite(camera.Near) || camera.Near < 0 || camera.Perspective && camera.Near == 0 {
|
||||
return nil, fmt.Errorf("invalid PDF 3D near distance")
|
||||
}
|
||||
if projection.Far != nil {
|
||||
camera.ClipFar = true
|
||||
camera.Far = *projection.Far
|
||||
if !finite(camera.Far) {
|
||||
return nil, fmt.Errorf("invalid PDF 3D far distance")
|
||||
}
|
||||
if camera.Far < camera.Near {
|
||||
passes, camera.Far = nil, 0
|
||||
camera.ClipFar = false
|
||||
}
|
||||
}
|
||||
case "ANF":
|
||||
minimum, maximum, found, err := r.DepthRange()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
passes = nil
|
||||
}
|
||||
camera.Near = minimum
|
||||
if camera.Perspective {
|
||||
if maximum <= 0 {
|
||||
passes = nil
|
||||
}
|
||||
camera.Near = max(1e-300, maximum*1e-9)
|
||||
if minimum > 0 {
|
||||
camera.Near = max(1e-300, minimum/2)
|
||||
}
|
||||
}
|
||||
camera.Far = math.Nextafter(maximum, math.Inf(1))
|
||||
if !finite(camera.Far) || camera.Far <= camera.Near {
|
||||
camera.Far = 0
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid PDF 3D clipping style")
|
||||
}
|
||||
if _, err := r.SelectView(render); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if source.Lighting.Subtype != "Artwork" && source.Lighting.Subtype != "" {
|
||||
if err := r.Reserve(uint64(len(source.Lighting.Lights)), 56); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
render.Lighting = &U3DLighting{DiffuseOffset: source.Lighting.Ambient, Lights: make([]U3DDirectionalLight, len(source.Lighting.Lights))}
|
||||
for i, light := range source.Lighting.Lights {
|
||||
if i%256 == 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
direction := light.Direction
|
||||
if light.CameraAttached {
|
||||
direction = [3]float64{0, 0, 1}
|
||||
}
|
||||
render.Lighting.Lights[i] = U3DDirectionalLight{Color: light.Color, Direction: direction, CameraRelative: light.CameraAttached}
|
||||
}
|
||||
}
|
||||
return r.Render(render, passes)
|
||||
}
|
||||
|
||||
// pdfThreeDCamera 解析PDF相机来源并保留模型视图的绘制轮次
|
||||
// 入参: ctx 取消上下文, model 三维模型, renderer 场景实例, source 相机来源
|
||||
// 返回: U3DCamera 相机变换, string 视图节点, error 路径或变换错误
|
||||
func pdfThreeDCamera(ctx context.Context, model *U3DModel, renderer *basethreed.Renderer, source pdfgo.ThreeDCamera) (U3DCamera, string, error) {
|
||||
var camera U3DCamera
|
||||
selected := -1
|
||||
for i, node := range model.Nodes {
|
||||
if i%256 == 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return camera, "", err
|
||||
}
|
||||
}
|
||||
if node.Kind == "View" {
|
||||
selected = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if source.Source == "M" {
|
||||
m := source.Matrix
|
||||
camera.ToWorld = [16]float64{m[0], m[1], m[2], 0, m[3], m[4], m[5], 0, m[6], m[7], m[8], 0, m[9], m[10], m[11], 1}
|
||||
} else {
|
||||
if source.Source != "" && source.Source != "U3D" || source.Source == "U3D" && len(source.Path) == 0 {
|
||||
return camera, "", fmt.Errorf("invalid PDF 3D camera source")
|
||||
}
|
||||
if source.Source == "U3D" {
|
||||
selected = -1
|
||||
for i, node := range model.Nodes {
|
||||
if i%256 == 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return camera, "", err
|
||||
}
|
||||
}
|
||||
if node.Name == source.Path[len(source.Path)-1] {
|
||||
selected = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if selected < 0 || model.Nodes[selected].Kind != "View" {
|
||||
return camera, "", fmt.Errorf("missing PDF 3D camera node")
|
||||
}
|
||||
var err error
|
||||
camera.ToWorld, err = renderer.ViewCamera(selected, source.Path, source.Source == "U3D")
|
||||
if errors.Is(err, basethreed.ErrAmbiguousCamera) {
|
||||
return camera, "", &pdfgo.UnsupportedError{Feature: "ambiguous 3D camera instance"}
|
||||
}
|
||||
if errors.Is(err, basethreed.ErrCameraNotFound) {
|
||||
return camera, "", fmt.Errorf("missing PDF 3D camera path")
|
||||
}
|
||||
if err != nil {
|
||||
return camera, "", err
|
||||
}
|
||||
}
|
||||
view := ""
|
||||
if selected >= 0 {
|
||||
view = model.Nodes[selected].Name
|
||||
}
|
||||
return camera, view, nil
|
||||
}
|
||||
|
||||
// pdfThreeDNodeStates 将PDF节点参数转为场景覆盖,转换缓存计入绘制预算
|
||||
// 入参: ctx 取消上下文, nodes 节点参数, options 绘制选项
|
||||
// 返回: error 预算或取消错误
|
||||
func pdfThreeDNodeStates(ctx context.Context, nodes []pdfgo.ThreeDNode, options *U3DRenderOptions) error {
|
||||
if len(nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
limit := options.MaxMemoryBytes
|
||||
if limit == 0 {
|
||||
limit = basethreed.DefaultBytes
|
||||
}
|
||||
if limit <= 0 || uint64(len(nodes)) >= uint64(limit)/168 {
|
||||
return fmt.Errorf("PDF 3D node memory exceeds limit")
|
||||
}
|
||||
options.MaxMemoryBytes = limit - int64(len(nodes))*168
|
||||
options.Nodes = make([]U3DNodeState, len(nodes))
|
||||
for i, node := range nodes {
|
||||
if i%256 == 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
state := U3DNodeState{Name: node.Name, Opacity: node.Opacity, Visible: node.Visible}
|
||||
if node.Matrix != nil {
|
||||
m := node.Matrix
|
||||
state.Matrix = &[16]float64{m[0], m[1], m[2], 0, m[3], m[4], m[5], 0, m[6], m[7], m[8], 0, m[9], m[10], m[11], 1}
|
||||
}
|
||||
options.Nodes[i] = state
|
||||
}
|
||||
return nil
|
||||
return codecpdf.RenderThreeD(ctx, model, source, codecpdf.ThreeDRenderOptions{
|
||||
Width: options.Width, Height: options.Height,
|
||||
MaxMemoryBytes: options.MaxMemoryBytes, MaxInstances: options.MaxInstances,
|
||||
ResolveTexture: options.ResolveTexture, Nodes: options.Nodes, Time: options.Time,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user