/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include "OdaCommon.h" #include "DynamicLinker.h" #include "RxThreadPoolService.h" #include "Ge/GeQuaternion.h" #include "Gs/GsViewportProperties.h" #include "Gs/GsRenderSettingsProperties.h" #include "Gs/GsBaseVectorizer.h" #include "Gi/GiEnvironmentTraitsData.h" #include "Gi/GiRasterWrappers.h" #include "GsEnvironmentBackgroundImpl.h" // OdGsEnvironmentBackgroundImpl OdGsEnvironmentBackgroundImpl::OdGsEnvironmentBackgroundImpl(const OdGsBackgroundModule *pModule) : OdGsBackgroundImpl(pModule) { } OdGsEnvironmentBackgroundImpl::~OdGsEnvironmentBackgroundImpl() { if (dest.image()) delete []dest.image(); } struct OdGsEnvironmentBackgroundContext { OdGiImageBGRA32 &source, &dest; const OdGsEnvironmentBackgroundImpl::Params ¶ms; const OdGeVector3d startVec; double yAngle, xAngle; bool bCubeMap, bUseXform; OdGeMatrix3d xForm; OdGsEnvironmentBackgroundContext(OdGiImageBGRA32 &_source, OdGiImageBGRA32 &_dest, const OdGsEnvironmentBackgroundImpl::Params &_params, const OdGeVector3d &_startVec, double yRot, double xRot, bool bCMap) : source(_source), dest(_dest), params(_params), startVec(_startVec), yAngle(yRot), xAngle(xRot), bCubeMap(bCMap) , bUseXform(OdNonZero(_params.m_longAng) || OdNonZero(_params.m_latAng)) { if (bUseXform) { const double longSin = sin(params.m_longAng), longCos = cos(params.m_longAng), latSin = sin(params.m_latAng), latCos = cos(params.m_latAng); xForm.setCoordSystem(OdGePoint3d::kOrigin, OdGeVector3d(longCos, -longSin * latCos, -longSin * latSin), OdGeVector3d(longSin, longCos * latCos, longCos * latSin), OdGeVector3d(0.0, -latSin, latCos)); } } const OdGeVector3d &upVector() const { return params.m_upVector; } const OdGeVector3d &xVector() const { return params.m_xVector; } }; struct OdGsEnvironmentBackgroundTaskContext { const OdGsEnvironmentBackgroundContext &context; OdUInt32 nXFrom, nXLen, nYFrom, nYLen; OdGsEnvironmentBackgroundTaskContext(const OdGsEnvironmentBackgroundContext &_context, OdUInt32 xFrom, OdUInt32 xLen, OdUInt32 yFrom, OdUInt32 yLen) : context(_context), nXFrom(xFrom), nXLen(xLen), nYFrom(yFrom), nYLen(yLen) { } void execute() { if (context.bCubeMap) processCubeMap(); else processSphereMap(); } protected: void processSphereMap() { const OdGeVector3d xAxis = OdGeVector3d(context.xVector()).rotateBy(-context.yAngle * (context.dest.width() / 2), context.upVector()); for (OdUInt32 nY = nYFrom; nY < nYLen; nY++) { const OdGeVector3d vDir = OdGeVector3d(context.startVec).rotateBy(context.xAngle * (double)nY, xAxis); for (OdUInt32 nX = nXFrom; nX < nXLen; nX++) { OdGeVector3d dir = -OdGeVector3d(vDir).rotateBy(context.yAngle * (double)nX, context.upVector()); if (context.bUseXform) dir.transformBy(context.xForm); const double phi = (OdaPI + OD_ATAN2(dir.x, dir.y)) / Oda2PI, theta = OD_ACOS(-dir.z) / OdaPI; context.dest.image()[nX + nY * context.dest.width()] = context.source.image()[OdUInt32(phi * context.source.width()) + OdUInt32(theta * context.source.height()) * context.source.width()]; } } } void processCubeMap() { enum CubeSide { kLeft = 0, kRight, kTop, kDown, kFront, kBack, kNumSides }; const OdGeQuaternion faces[kNumSides] = { OdGeQuaternion( 1.0, 0.0, 0.0, -1.0), /* kLeft */ OdGeQuaternion(-1.0, 0.0, 0.0, -1.0), /* kRight */ OdGeQuaternion( 0.0, 1.0, 0.0, -1.0), /* kTop */ OdGeQuaternion( 0.0, -1.0, 0.0, -1.0), /* kDown */ OdGeQuaternion( 0.0, 0.0, 1.0, -1.0), /* kFront */ OdGeQuaternion( 0.0, 0.0, -1.0, -1.0) /* kBack */ }; const OdUInt32 wip = context.source.width() / 2, hip = context.source.height() / 3; const double wScale = double(wip) / 2, hScale = double(hip) / 2; const OdGeVector3d xAxis = OdGeVector3d(context.xVector()).rotateBy(-context.yAngle * (context.dest.width() / 2), context.upVector()); for (OdUInt32 nY = nYFrom; nY < nYLen; nY++) { const OdGeVector3d vDir = OdGeVector3d(context.startVec).rotateBy(context.xAngle * (double)nY, xAxis); for (OdUInt32 nX = nXFrom; nX < nXLen; nX++) { OdGeVector3d dir = -OdGeVector3d(vDir).rotateBy(context.yAngle * (double)nX, context.upVector()); if (context.bUseXform) dir.transformBy(context.xForm); const OdGeVector3d p(-dir.x, -dir.z, dir.y); OdGeVector3d q; // Find which face the vector intersects int found = -1; for (int k = 0; k < kNumSides; k++) { const double denom = -(faces[k].w * p.x + faces[k].x * p.y + faces[k].y * p.z); // Is p parallel to face? if (fabs(denom) < 0.000001) continue; // Is the intersection on the back pointing ray? double mu; if ((mu = faces[k].z / denom) < 0.0) continue; // q is the intersection point q = p * mu; // Find out which face it is on switch (k) { case kLeft: case kRight: if (q.y <= 1.0 && q.y >= -1.0 && q.z <= 1.0 && q.z >= -1.0) found = k; break; case kFront: case kBack: if (q.x <= 1.0 && q.x >= -1.0 && q.y <= 1.0 && q.y >= -1.0) found = k; break; case kTop: case kDown: if (q.x <= 1.0 && q.x >= -1.0 && q.z <= 1.0 && q.z >= -1.0) found = k; break; default: ODA_FAIL(); } if (found >= 0) break; } if (found < 0 || found > 5) { // Didn't find an intersecting face - shouldn't happen! ODA_FAIL_ONCE(); continue; } // Determine u, v coordinates int u = 0, v = 0; switch (found) { case kLeft: u = int(wScale * (q.z + 1)); v = int(hScale * (q.y + 1)); break; case kRight: u = int(wScale * (1 - q.z)); v = int(hScale * (q.y + 1)); break; case kFront: u = int(wScale * (1 - q.x)); v = int(hScale * (q.y + 1)); break; case kBack: u = int(wScale * (q.x + 1)); v = int(hScale * (q.y + 1)); break; case kDown: u = int(wScale * (1 - q.x)); v = int(hScale * (1 + q.z)); break; case kTop: u = int(wScale * (1 - q.x)); v = int(hScale * (1 - q.z)); break; default: ODA_FAIL(); } if (u < 0 || u >= int(wip) || v < 0 || v >= int(hip)) { // Illegal (u,v) coordinate on face ODA_FAIL_ONCE(); continue; } // Final movement to tile switch (found) { case kLeft: u += wip; break; case kFront: v += hip * 2; break; case kBack: u += wip; v += hip * 2; break; case kTop: u += wip; v += hip; break; case kDown: v += hip; break; default: break; } context.dest.image()[nX + nY * context.dest.width()] = context.source.image()[OdUInt32(u) + OdUInt32(context.source.height() - 1 - v) * context.source.width()]; } } } }; class OdGsEnvironmentBackgroundMtTask : public OdStaticRxObject { protected: OdGsEnvironmentBackgroundTaskContext m_context; public: OdGsEnvironmentBackgroundMtTask(const OdGsEnvironmentBackgroundContext &ctx) : m_context(ctx, 0, 0, 0, 0) { } void set(OdUInt32 xFrom, OdUInt32 xLen, OdUInt32 yFrom, OdUInt32 yLen) { m_context.nXFrom = xFrom; m_context.nXLen = xLen; m_context.nYFrom = yFrom; m_context.nYLen = yLen; } void apcEntryPoint(OdRxObject* /*pMessage*/) override { m_context.execute(); } }; class OdGsRenderEnvironmentTraitsImpl : public OdGiRenderEnvironmentTraits { protected: OdGiRenderEnvironmentTraitsData m_data; public: void getData(OdGiRenderEnvironmentTraitsData& data) const { data = m_data; } const OdGiRenderEnvironmentTraitsData &data() const { return m_data; } void setEnable(bool bEnable) override { m_data.setEnable(bEnable); } bool enable(void) const override { return m_data.enable(); } void setIsBackground(bool bEnable) override { m_data.setIsBackground(bEnable); } bool isBackground(void) const override { return m_data.isBackground(); } void setFogColor(const OdCmEntityColor &color) override { m_data.setFogColor(color); } OdCmEntityColor fogColor(void) const override { return m_data.fogColor(); } void setNearDistance(double nearDist) override { m_data.setNearDistance(nearDist); } double nearDistance(void) const override { return m_data.nearDistance(); } void setFarDistance(double farDist) override { m_data.setFarDistance(farDist); } double farDistance(void) const override { return m_data.farDistance(); } void setNearPercentage(double nearPct) override { m_data.setNearPercentage(nearPct); } double nearPercentage(void) const override { return m_data.nearPercentage(); } void setFarPercentage(double farPct) override { m_data.setFarPercentage(farPct); } double farPercentage(void) const override { return m_data.farPercentage(); } void setEnvironmentMap(const OdGiMaterialTexture *map) override { m_data.setEnvironmentMap(map); } OdGiMaterialTexture *environmentMap(void) const override { return m_data.environmentMap().get(); } }; void OdGsEnvironmentBackgroundImpl::display(OdGsBaseVectorizer& view, const OdGiDrawable *pDrawable, OdGiBackgroundTraitsData *pBackgroundTraits, OdGsPropertiesDirectRenderOutput *pdro) { #ifdef OD_GSBACKGROUND_VERBOSE_ON ODA_TRACE(L"OdGsEnvironmentBackgroundImpl::display\n"); #endif OdGiMaterialTexturePtr pTexture; OdGsRenderEnvironmentPropertiesPtr pREnv = view.view().getViewportPropertiesForType(OdGsProperties::kRenderEnvironment); if (pREnv.isNull()) { OdGsViewportPropertiesPtr pViewProps = view.view().getViewportPropertiesForType(OdGsProperties::kViewport); if (pViewProps->viewportTraitsData()->renderEnvironment()) { OdStaticRxObject envTraits; OdGiDrawablePtr pEnvironmentDrawable = view.context()->openDrawable(pViewProps->viewportTraitsData()->renderEnvironment()); if (!pEnvironmentDrawable.isNull()) pEnvironmentDrawable->setAttributes(&envTraits); pTexture = envTraits.environmentMap(); } } else { pTexture = pREnv->renderEnvironmentTraitsData()->environmentMap(); } if (!pTexture.isNull()) { bool bDiff = false; if (texture.isNull() != pTexture.isNull() || !(*texture == *pTexture)) { texture = pTexture->clone(); textureData = OdGiMaterialTextureEntry::createObject(); textureData->setGiMaterialTexture(OdGiMaterialTextureData::DevDataVariant(), OdGiMaterialTextureData::defaultTextureDataImplementationDesc(), *view.context(), texture); if (textureData->isTextureInitialized() && !textureData->textureData().isNull() && textureData->textureData()->haveData()) { OdUInt32 imageWidth, imageHeight; OdGiPixelBGRA32Array pImageData; textureData->textureData()->textureData(pImageData, imageWidth, imageHeight); source.setImage(imageWidth, imageHeight, const_cast(pImageData.getPtr())); } else source.setImage(0, 0, nullptr); bDiff = true; } if (source.image()) { OdGiEnvironmentBackgroundTraitsData *bgt = static_cast(pBackgroundTraits); OdGePoint3d rect[5], scs[4]; const OdGeMatrix3d e2o = view.eyeToOutputTransform(), e2s = view.view().eyeToScreenMatrix(); fillBackgroundRect(view.view(), rect); for (int i = 0; i < 5; i++) { if (i < 4) { scs[i] = rect[i]; scs[i].transformBy(e2s); } rect[i].transformBy(e2o); } const OdUInt32 nWidth = static_cast(OdRound((scs[1] - scs[0]).length())); const OdUInt32 nHeight = static_cast(OdRound((scs[3] - scs[0]).length())); if (nWidth > 0 && nHeight > 0) { if (dest.width() != nWidth || dest.height() != nHeight) { if (dest.image()) delete []dest.image(); dest.setImage(nWidth, nHeight, new OdGiPixelBGRA32[nWidth * nHeight]); bDiff = true; } if (raster.isNull()) raster = OdGiRasterImageBGRA32::createObject(&dest); #if 0 // Test gradient generation const double deltaX = 255.0 / nWidth, deltaY = 255.0 / nHeight; for (OdUInt32 nY = 0; nY < nHeight; nY++) { for (OdUInt32 nX = 0; nX < nWidth; nX++) dest.image()[nX + nY * nWidth].setRGBA((OdUInt8)(deltaY * nY), (OdUInt8)(deltaX * nX), 0, 255); } #endif Params lps(view.viewDir(), view.getCameraUpVector(), view.view().xVector(), ((!bgt->fovOverride()) ? view.view().lensLengthToFOV(view.view().lensLength()) : bgt->fovOverrideAngle()) / 2, view.view().windowAspect(), bgt->longitudeRotation(), bgt->latitudeRotation()); if (lps != params || bDiff) { params = lps; bDiff = true; const double hAspect = (lps.m_aspect > 1.0) ? lps.m_aspect : 1.0, vAspect = (lps.m_aspect < 1.0) ? 1.0 / lps.m_aspect : 1.0; OdGsEnvironmentBackgroundContext procCtx(source, dest, lps, OdGeVector3d(lps.m_dir).rotateBy(lps.m_fov * hAspect, lps.m_upVector). rotateBy(-lps.m_fov * vAspect, OdGeVector3d(lps.m_xVector).rotateBy(lps.m_fov * hAspect, lps.m_upVector)), (-lps.m_fov * hAspect) / (double(nWidth) / 2), (lps.m_fov * vAspect) / (double(nHeight) / 2), ((double)source.width() / source.height() < 1.0) && !(source.width() % 2) && !(source.height() % 3)); const OdUInt64 fullSize = OdUInt64(nWidth) * nHeight, subdivLimit = 512 * 512; OdUInt32 numThreads = 1; if (fullSize > subdivLimit) { if (threadPool.isNull()) threadPool = ::odrxDynamicLinker()->loadApp(OdThreadPoolModuleName); if (!threadPool.isNull()) { numThreads = OdUInt32(fullSize / subdivLimit); if (numThreads < 2) numThreads = 2; else if (numThreads > 4) numThreads = 4; } } if (numThreads > 1) { OdApcQueuePtr pQueue = threadPool->newMTQueue(ThreadsCounter::kNoAttributes, numThreads, kMtQueueAllowExecByMain); OdGsEnvironmentBackgroundMtTask mtTask1(procCtx), mtTask2(procCtx), mtTask3(procCtx), mtTask4(procCtx); // macos build fix OdGsEnvironmentBackgroundMtTask *mtTasks[4] = { &mtTask1, &mtTask2, &mtTask3, &mtTask4 }; if (hAspect > vAspect) { OdUInt32 nXs = nWidth / numThreads, nRems = nWidth % numThreads; for (OdUInt32 nThread = 0, nX = 0; nThread < numThreads; nThread++) { const OdUInt32 nLen = (!nThread) ? nXs + nRems : nXs; mtTasks[nThread]->set(nX, nX + nLen, 0, nHeight); nX += nLen; pQueue->addEntryPoint(mtTasks[nThread]); } } else { OdUInt32 nYs = nHeight / numThreads, nRems = nHeight % numThreads; for (OdUInt32 nThread = 0, nY = 0; nThread < numThreads; nThread++) { const OdUInt32 nLen = (!nThread) ? nYs + nRems : nYs; mtTasks[nThread]->set(0, nWidth, nY, nY + nLen); nY += nLen; pQueue->addEntryPoint(mtTasks[nThread]); } } pQueue->wait(); } else OdGsEnvironmentBackgroundTaskContext(procCtx, 0, nWidth, 0, nHeight).execute(); } // Output RenderingModeHolder holder = setRenderingMode(view); const OdGeVector3d u = rect[1] - rect[0], v = rect[3] - rect[0]; if (pdro != NULL && GETBIT(pdro->directRenderOutputFlags(), OdGsPropertiesDirectRenderOutput::DirectRender_Image)) { OdGsPropertiesDirectRenderOutput::DirectRenderImageParams imageParams; imageParams.pDrawable = pDrawable; imageParams.bUpdate = bDiff; pdro->directRenderOutputImage(rect, raster, imageParams); } else view.rasterImageDc(rect[0], u / (double)raster->pixelWidth(), v / (double)raster->pixelHeight(), raster, NULL, 0); restoreRenderingMode(view, holder); } } } } //