/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// Calculates the light directions for the additional lights, which are used to emulate soft // lights of MS Office. Method needs to be documented in the Wiki // https://wiki.documentfoundation.org/Development/ODF_Implementer_Notes in part // List_of_LibreOffice_ODF_implementation-defined_items // The method expects vector rLight to be normalized and results normalized vectors. void lcl_SoftLightsDirection(const basegfx::B3DVector& rLight, basegfx::B3DVector& rSoftUp,
basegfx::B3DVector& rSoftDown, basegfx::B3DVector& rSoftRight,
basegfx::B3DVector& rSoftLeft)
{
constexpr double fAngle = basegfx::deg2rad(60); // angle between regular light and soft light
// We first create directions around (0|0|1) and then rotate them to the light position.
rSoftUp = basegfx::B3DVector(0.0, sin(fAngle), cos(fAngle));
rSoftDown = basegfx::B3DVector(0.0, -sin(fAngle), cos(fAngle));
rSoftRight = basegfx::B3DVector(sin(fAngle), 0.0, cos(fAngle));
rSoftLeft = basegfx::B3DVector(-sin(fAngle), 0.0, cos(fAngle));
basegfx::B3DHomMatrix aRotateMat;
aRotateMat.rotate(0.0, 0.0, M_PI_4); if (rLight.getX() == 0.0 && rLight.getZ() == 0.0)
{ // Special case with light from top or bottom if (rLight.getY() >= 0.0)
aRotateMat.rotate(-M_PI_2, 0.0, 0.0); else
aRotateMat.rotate(M_PI_2, 0.0, 0.0);
} else
{ // Azimuth from z-axis to x-axis. (0|0|1) to (1|0|0) is 90deg. double fAzimuth = atan2(rLight.getX(), rLight.getZ()); // Elevation from xz-plane to y-axis. (0|0|1) to (0|1|0) is 90deg. double fElevation = atan2(rLight.getY(), std::hypot(rLight.getX(), rLight.getZ()));
aRotateMat.rotate(-fElevation, fAzimuth, 0.0);
}
if ( rSdrObjCustomShape.getSdrModelFromSdrObject().GetScaleUnit() != MapUnit::Map100thMM )
{
DBG_ASSERT( rSdrObjCustomShape.getSdrModelFromSdrObject().GetScaleUnit() == MapUnit::MapTwip, "EnhancedCustomShape3d::Current MapMode is Unsupported" ); // But we could use MapToO3tlUnit from <tools/UnitConversion> ... ?
fMap *= o3tl::convert(1.0, o3tl::Length::mm100, o3tl::Length::twip);
pMap = &fMap;
}
if ( GetBool( rGeometryItem, u"Extrusion"_ustr, false ) )
{ bool bIsMirroredX(rSdrObjCustomShape.IsMirroredX()); bool bIsMirroredY(rSdrObjCustomShape.IsMirroredY());
tools::Rectangle aSnapRect(rSdrObjCustomShape.GetLogicRect());
Degree100 nObjectRotation(rSdrObjCustomShape.GetRotateAngle()); if ( nObjectRotation )
{ double a = toRadians(36000_deg100 - nObjectRotation);
tools::Long dx = aSnapRect.Right() - aSnapRect.Left();
tools::Long dy = aSnapRect.Bottom()- aSnapRect.Top();
Point aP( aSnapRect.TopLeft() );
RotatePoint( aP, rSdrObjCustomShape.GetSnapRect().Center(), sin( a ), cos( a ) );
aSnapRect.SetLeft( aP.X() );
aSnapRect.SetTop( aP.Y() );
aSnapRect.SetRight( aSnapRect.Left() + dx );
aSnapRect.SetBottom( aSnapRect.Top() + dy );
}
Point aCenter( aSnapRect.Center() );
// tdf#146360 If the ItemSet of the source SdrObject has a parent // (which means it has a StyleSheet), we need to do some old-style // 'BurnInStyleSheetAttributes' action. // That means to set all Items which are set in the StyleSheet // directly in the ItemSet. // This is okay here since the 3D SdrObjects created are // placeholders that get rendered, but never reach the // surface/the user. If attributes for the source SdrObject // change, these will be recreated. // The problem is that while "aSet" still has a ptr to the style's // ItemSet, this gets lost at the ItemSet of the SdrObject when // an ItemSet gets set at the 3D SdrObject, like in diverse // SetMergedItemSet calls below. This leads to fetching the wrong // (default) FillBitmap in the calls p3DObj->GetMergedItem below // (which is 32x32 white, that's what you see without the fix). // This could also be fixed (tried it) by either // - using rSdrObjCustomShape.GetMergedItem // - setting the StyleSheet at 3D SdrObjects ASAP (done at caller) // but both solutions contain the risk to not find all places, so // it's just more safe to merge the StyleSheet attributes to the // ItemSet used for the whole creation. if(nullptr != aSet.GetParent())
{
SfxWhichIter aIter(aSet);
sal_uInt16 nWhich(aIter.FirstWhich()); const SfxPoolItem *pItem(nullptr);
while(nWhich)
{ // this may look at 1st look like doing nothing, but it converts // items set in parent/style to SfxItemState::SET items in the // ItemSet (see AttributeProperties::ForceStyleToHardAttributes()) if(SfxItemState::SET == aSet.GetItemState(nWhich, true, &pItem))
{
aSet.Put(*pItem);
}
nWhich = aIter.NextWhich();
}
aSet.SetParent(nullptr);
}
//SJ: vertical writing is not required, by removing this item no outliner is created
aSet.ClearItem( SDRATTR_TEXTDIRECTION );
// #i105323# For 3D AutoShapes, the shadow attribute has to be applied to each // created visualisation helper model shape individually. The shadow itself // will then be rendered from the 3D renderer correctly for the whole 3D scene // (and thus behind all objects of which the visualisation may be built). So, // do NOT remove it from the ItemSet here. // aSet.ClearItem(SDRATTR_SHADOW);
// If shapes are mirrored once (mirroring two times correct geometry again) // double-sided at the object and two-sided-lighting at the scene need to be set.
// #i122777# Also use double sided for two fill styles since there several 3d objects get // created with a depth of 0; one of them is the backside which needs double-sided to // get visible if(bUseTwoFillStyles || (bIsMirroredX && !bIsMirroredY) || (!bIsMirroredX && bIsMirroredY))
{
aSet.Put( makeSvx3DDoubleSidedItem( true ) );
pScene->GetProperties().SetObjectItem( makeSvx3DTwoSidedLightingItem( true ) );
}
}
if ( auto pPathObj = dynamic_cast<const SdrPathObj*>(pNext) )
{ const SfxItemSet& rSet = pNext->GetMergedItemSet(); bool bNeedToConvertToContour(false);
// do conversion only for single line objects; for all others a fill and a // line object get created. When we have fill, we want no line. That line has // always been there, but since it was never converted to contour, it kept // invisible (all this 'hidden' logic should be migrated to primitives). if(!bMultipleSubObjects)
{ const drawing::FillStyle eStyle(rSet.Get(XATTR_FILLSTYLE).GetValue());
// #i122777# depth 0 is okay for planes when using double-sided
rtl::Reference<E3dCompoundObject> p3DObj = new E3dExtrudeObj(
rSdrObjCustomShape.getSdrModelFromSdrObject(),
a3DDefaultAttr,
aPolyPoly,
bUseTwoFillStyles ? 0 : fDepth );
// #i122777# old adaptation of FillStyle bitmap size to 5-times the original size; this is not needed // anymore and was used in old times to male the fill look better when converting to 3D. Removed // from regular 3D objects for some time, also needs to be removed from CustomShapes
// #i122777# depth 0 is okay for planes when using double-sided
p3DObj = new E3dExtrudeObj(
rSdrObjCustomShape.getSdrModelFromSdrObject(),
a3DDefaultAttr,
std::move(aPolyPoly),
0);
basegfx::B3DHomMatrix aNewTransform( pScene->GetTransform() );
basegfx::B2DHomMatrix aPolyPolyTransform; // Apply flip and z-rotation to scene transformation (y up). At same time transform // aTotalPolyPoly (y down) which will be used for 2D boundRect of shape having 2D // transformations applied.
// API values use shape center as origin. Move scene so, that shape center is origin.
aNewTransform.translate( -aCenter.X(), aCenter.Y(), -fExtrusionBackward);
aPolyPolyTransform.translate(-aCenter.X(), -aCenter.Y());
// x- and y-rotation have an own rotation center. x- and y-value of rotation center are // fractions of shape size, z-value is in Hmm in property. Shape center is (0 0 0). // Values in property are in custom shape extrusion space with y-axis down. double fXRotate, fYRotate;
GetRotateAngle( rGeometryItem, fXRotate, fYRotate );
drawing::Direction3D aRotationCenterDefault( 0, 0, 0 );
drawing::Direction3D aRotationCenter( GetDirection3D( rGeometryItem, u"RotationCenter"_ustr, aRotationCenterDefault ) );
aRotationCenter.DirectionX *= aSnapRect.getOpenWidth();
aRotationCenter.DirectionY *= aSnapRect.getOpenHeight(); if (pMap)
{
aRotationCenter.DirectionZ *= *pMap;
}
aNewTransform.translate( -aRotationCenter.DirectionX, aRotationCenter.DirectionY, -aRotationCenter.DirectionZ ); if( fYRotate != 0.0 )
aNewTransform.rotate( 0.0, -fYRotate, 0.0 ); if( fXRotate != 0.0 )
aNewTransform.rotate( -fXRotate, 0.0, 0.0 );
aNewTransform.translate(aRotationCenter.DirectionX, -aRotationCenter.DirectionY, aRotationCenter.DirectionZ);
// oblique parallel projection is done by shearing the object, not by moving the camera if (eProjectionMode == drawing::ProjectionMode_PARALLEL)
{ double fSkew, fAlpha;
GetSkew( rGeometryItem, fSkew, fAlpha ); if ( fSkew != 0.0 )
{ double fInvTanBeta( fSkew / 100.0 ); if(fInvTanBeta)
{
aNewTransform.shearXY(
fInvTanBeta * cos(fAlpha),
fInvTanBeta * sin(fAlpha));
}
}
}
pScene->NbcSetTransform( aNewTransform );
// These values are used later again, so declare them outside the if-statement. They will // contain the absolute values of ViewPoint in 3D scene coordinate system, y-axis up. double fViewPointX = 0; // dummy values double fViewPointY = 0; double fViewPointZ = 25000; if (eProjectionMode == drawing::ProjectionMode_PERSPECTIVE)
{ double fOriginX, fOriginY; // Calculate BoundRect of shape, including flip and z-rotation, from aTotalPolyPoly.
tools::Rectangle aBoundAfter2DTransform; // aBoundAfter2DTransform has y-axis down.
basegfx::B2DRange aTotalPolyPolyRange(aTotalPolyPoly.getB2DRange());
aBoundAfter2DTransform.SetLeft(aTotalPolyPolyRange.getMinX());
aBoundAfter2DTransform.SetTop(aTotalPolyPolyRange.getMinY());
aBoundAfter2DTransform.SetRight(aTotalPolyPolyRange.getMaxX());
aBoundAfter2DTransform.SetBottom(aTotalPolyPolyRange.getMaxY());
// Property "Origin" in API is relative to bounding box of shape after 2D // transformations. Range is [-0.5;0.5] with center of bounding box as 0. // Resolve "Origin" fractions to length
GetOrigin( rGeometryItem, fOriginX, fOriginY );
fOriginX *= aBoundAfter2DTransform.GetWidth();
fOriginY *= aBoundAfter2DTransform.GetHeight(); // Resolve length to absolute value for 3D
fOriginX += aBoundAfter2DTransform.Center().X();
fOriginY += aBoundAfter2DTransform.Center().Y();
fOriginY = - fOriginY; // Scene is translated so that shape center is origin of coordinate system. // Translate point "Origin" too.
fOriginX -= aCenter.X();
fOriginY -= -aCenter.Y(); // API ViewPoint values are relative to point "Origin" and have y-axis down. // ToDo: These default ViewPoint values are used as default by MS Office. But ODF // default is (3500, -3500, 25000), details in tdf#146192.
drawing::Position3D aViewPointDefault( 3472, -3472, 25000 );
drawing::Position3D aViewPoint( GetPosition3D( rGeometryItem, u"ViewPoint"_ustr, aViewPointDefault, pMap ) );
fViewPointX = aViewPoint.PositionX + fOriginX;
fViewPointY = - aViewPoint.PositionY + fOriginY;
fViewPointZ = aViewPoint.PositionZ;
}
// now set correct camera position if (eProjectionMode == drawing::ProjectionMode_PARALLEL)
{
basegfx::B3DPoint _aLookAt( 0.0, 0.0, 0.0 );
basegfx::B3DPoint _aNewCamPos( 0.0, 0.0, 25000.0 );
rCamera.SetPosAndLookAt( _aNewCamPos, _aLookAt );
pScene->SetCamera( rCamera );
} else
{
basegfx::B3DPoint _aLookAt(fViewPointX, fViewPointY, 0.0);
basegfx::B3DPoint aNewCamPos(fViewPointX, fViewPointY, fViewPointZ);
rCamera.SetPosAndLookAt( aNewCamPos, _aLookAt );
pScene->SetCamera( rCamera );
}
// NbcSetTransform has not updated the scene 2D rectangles. // Idea: Get a bound volume as polygon from bound rectangle of shape without 2D // transformations. Calculate its projection to the XY-plane. Then calculate the bounding // rectangle of the projection and convert this rectangle back to absolute 2D coordinates. // Set that as 2D rectangle of the scene. const tools::Polygon aPolygon(aBoundRect2d); // y-up
basegfx::B3DPolygon aPolygonBoundVolume; // y-down, scene coordinates for (sal_uInt16 i = 0; i < 4; i++ )
{
aPolygonBoundVolume.append(basegfx::B3DPoint(aPolygon[i].X(), -aPolygon[i].Y(), 0));
} for (sal_uInt16 i = 0; i < 4; i++ )
{
aPolygonBoundVolume.append(basegfx::B3DPoint(aPolygon[i].X(), -aPolygon[i].Y(), fDepth));
}
aPolygonBoundVolume.transform(aNewTransform);
// projection
tools::Polygon a2DProjectionResult(8); // in fact 3D points with z=0 for (sal_uInt16 i = 0; i < 8; i++ )
{ const basegfx::B3DPoint aPoint3D(aPolygonBoundVolume.getB3DPoint(i));
if (eProjectionMode == drawing::ProjectionMode_PARALLEL)
{
a2DProjectionResult[i].setX(aPoint3D.getX());
a2DProjectionResult[i].setY(aPoint3D.getY());
} else
{ // skip point if line from viewpoint to point is parallel to xy-plane if (double fDiv = aPoint3D.getZ() - fViewPointZ; fDiv != 0.0)
{ double f = (- fViewPointZ) / fDiv; double fX = (aPoint3D.getX() - fViewPointX) * f + fViewPointX; double fY = (aPoint3D.getY() - fViewPointY) * f + fViewPointY;;
a2DProjectionResult[i].setX(static_cast<sal_Int32>(fX));
a2DProjectionResult[i].setY(static_cast<sal_Int32>(fY));
}
}
} // Convert to y-axis down for (sal_uInt16 i = 0; i < 8; i++ )
{
a2DProjectionResult[i].setY(- a2DProjectionResult[i].Y());
} // Shift back to shape center
a2DProjectionResult.Translate(aCenter);
// tdf#160421 a single flip inverts the light directions currently (March 2024). So invert // their directions here for rendering. if (bIsMirroredX != bIsMirroredY)
{
aLight1Vector *= -1.0;
aLight2Vector *= -1.0;
}
// Light Intensity
// For "FirstLight" the 3D-Scene light "1" is regularly used. In case of surface "Matte" // the light 4 is used instead. For "SecondLight" the 3D-Scene light "2" is regularly used. // In case first or second light is not harsh, the lights 5 to 8 are used in addition // to get a soft light appearance. // The 3D-Scene light "3" is currently not used.
// ODF default 66%. MS Office default 38000/65536=0.579 is set in import filter. double fLight1Intensity = GetDouble(rGeometryItem, u"FirstLightLevel"_ustr, 66) / 100.0; // ODF and MS Office have both default 'true'. bool bFirstLightHarsh = GetBool(rGeometryItem, u"FirstLightHarsh"_ustr, true); // ODF default 66%. MS Office default 38000/65536=0.579 is set in import filter double fLight2Intensity = GetDouble(rGeometryItem, u"SecondLightLevel"_ustr, 66) / 100.0; // ODF has default 'true'. MS Office default 'false' is set in import. bool bSecondLightHarsh = GetBool(rGeometryItem, u"SecondLightHarsh"_ustr, true);
// ODF default 33%. MS Office default 20000/65536=0.305 is set in import filter. double fAmbientIntensity = GetDouble(rGeometryItem, u"Brightness"_ustr, 33) / 100.0;
double fLight1IntensityForSpecular(fLight1Intensity); // remember original value if (!bFirstLightHarsh || !bSecondLightHarsh) // might need softing lights
{ bool bNeedSoftLights(false); // catch case of lights with zero intensity.
basegfx::B3DVector aLight5Vector;
basegfx::B3DVector aLight6Vector;
basegfx::B3DVector aLight7Vector;
basegfx::B3DVector aLight8Vector; // The needed light intensities depend on the angle between regular light and // additional lights, currently for 60deg.
Color aHoriSoftLightColor;
Color aVertSoftLightColor;
if (!bSecondLightHarsh && fLight2Intensity > 0.0
&& (bFirstLightHarsh || fLight1Intensity == 0.0)) // only second light soft
{ // That is default for shapes generated in the UI, for LO and MS Office as well.
bNeedSoftLights = true; double fLight2SoftIntensity = fLight2Intensity * 0.40;
aHoriSoftLightColor = Color(basegfx::BColor(fLight2SoftIntensity).clamp());
aVertSoftLightColor = aHoriSoftLightColor;
fLight2Intensity *= 0.2;
// ToDo: MSO seems to add half of the surplus to ambient color. ODF restricts value to <1. if (fLight1Intensity > 1.0)
{
fAmbientIntensity += (fLight1Intensity - 1.0) / 2.0;
}
// ToDo: How to handle fAmbientIntensity larger 1.0 ? Perhaps lighten object color?
// Now set the regularly 3D-scene light attributes.
Color aAmbientColor(basegfx::BColor(fAmbientIntensity).clamp());
pScene->GetProperties().SetObjectItem(makeSvx3DAmbientcolorItem(aAmbientColor));
pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection1Item(aLight1Vector));
pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff1Item(fLight1Intensity > 0.0));
Color aLight1Color(basegfx::BColor(fLight1Intensity).clamp());
pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor1Item(aLight1Color));
pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection2Item(aLight2Vector));
pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff2Item(fLight2Intensity > 0.0));
Color aLight2Color(basegfx::BColor(fLight2Intensity).clamp());
pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor2Item(aLight2Color));
// Object reactions on light // Diffusion, Specular-Color and -Intensity are object properties, not scene properties. // Surface flag "Metal" is an object property too.
// Property "Diffusion" would correspond to style attribute "drd3:diffuse-color". // But that is not implemented. We cannot ignore the attribute because MS Office sets // attribute c3DDiffuseAmt to 43712 (Type Fixed 16.16, approx 66,9%) instead of MSO // default 65536 (100%), if the user sets surface 'Metal' in the UI of MS Office. // We will change the material color of the 3D object as ersatz. // ODF data type is percent with default 0%. MSO default is set in import filter. double fDiffusion = GetDouble(rGeometryItem, u"Diffusion"_ustr, 0.0) / 100.0;
// ODF standard specifies for value true: "the specular color for the shading of an // extruded shape is gray (red, green and blue values of 200) instead of white and 15% is // added to the specularity." // Neither 'specularity' nor 'specular color' is clearly defined in the standard. ODF term // 'specularity' seems to correspond to UI field 'Specular Intensity' for 3D scenes. // MS Office uses current material color in case 'Metal' is set. To detect, whether // rendering similar to MS Office has to be used the property 'MetalType' is used. It is // set on import and in the extrusion bar. bool bMetal = GetBool(rGeometryItem, u"Metal"_ustr, false);
sal_Int16 eMetalType(
GetMetalType(rGeometryItem, drawing::EnhancedCustomShapeMetalType::MetalODF)); bool bMetalMSCompatible
= eMetalType == drawing::EnhancedCustomShapeMetalType::MetalMSCompatible;
// Property "Specularity" corresponds to 3D object style attribute dr3d:specular-color. double fSpecularity = GetDouble(rGeometryItem, u"Specularity"_ustr, 0) / 100.0;
// MS Office seems to render as if 'Specular Color' = Specularity * Light1Intensity. double fShadingFactor = fLight1IntensityForSpecular * fSpecularity;
Color aSpecularCol(basegfx::BColor(fShadingFactor).clamp()); // In case of bMetalMSCompatible the color will be recalculated in the below loop.
// Shininess ODF default 50 (unit %). MS Office default 5, import filter makes *10. // Shininess corresponds to "Specular Intensity" with the nonlinear relationship // "Specular Intensity" = 2^c3DShininess = 2^("Shininess" / 10) double fShininess = GetDouble(rGeometryItem, u"Shininess"_ustr, 50) / 10.0;
fShininess = std::clamp<double>(pow(2, fShininess), 0.0, 100.0);
sal_uInt16 nIntensity = static_cast<sal_uInt16>(basegfx::fround(fShininess)); if (bMetal && !bMetalMSCompatible)
{
nIntensity += 15; // as specified in ODF
nIntensity = std::clamp<sal_uInt16>(nIntensity, 0, 100);
}
// Change material color as ersatz for missing style attribute "drd3:diffuse-color". // For this ersatz we exclude case fDiffusion == 0.0, because for older documents this // attribute is not written out to draw:extrusion-diffusion and ODF default 0 would // produce black objects. const Color& rMatColor
= pNext->GetProperties().GetItem(XATTR_FILLCOLOR).GetColorValue();
Color aOldMatColor(rMatColor); if (fDiffusion > 0.0 && !basegfx::fTools::equalZero(fDiffusion)
&& !basegfx::fTools::equal(fDiffusion, 1.0))
{ // Occurs e.g. with MS surface preset 'Metal'.
sal_uInt16 nHue;
sal_uInt16 nSaturation;
sal_uInt16 nBrightness;
rMatColor.RGBtoHSB(nHue, nSaturation, nBrightness);
nBrightness
= static_cast<sal_uInt16>(static_cast<double>(nBrightness) * fDiffusion);
nBrightness = std::clamp<sal_uInt16>(nBrightness, 0, 100);
Color aNewMatColor = Color::HSBtoRGB(nHue, nSaturation, nBrightness);
pNext->GetProperties().SetObjectItem(XFillColorItem(u""_ustr, aNewMatColor));
}
// Using material color instead of gray in case of MS Office compatible rendering. if (bMetal && bMetalMSCompatible)
{
sal_uInt16 nHue;
sal_uInt16 nSaturation;
sal_uInt16 nBrightness;
aOldMatColor.RGBtoHSB(nHue, nSaturation, nBrightness);
nBrightness = static_cast<sal_uInt16>(static_cast<double>(nBrightness)
* fShadingFactor);
nBrightness = std::clamp<sal_uInt16>(nBrightness, 0, 100);
aSpecularCol = Color::HSBtoRGB(nHue, nSaturation, nBrightness);
}
// fSpecularity = 0 is used to indicate surface preset "Matte". if (basegfx::fTools::equalZero(fSpecularity))
{ // First light in LO 3D engine is always specular, all other lights are never specular. // We copy light1 values to light4 and use it instead of light1 in the 3D scene.
pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff1Item(false));
pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff4Item(true));
pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor4Item(aLight1Color));
pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection4Item(aLight1Vector));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.